前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >带你“深入”防抖

带你“深入”防抖

作者头像
ClyingDeng
发布2023-03-04 17:55:50
4890
发布2023-03-04 17:55:50
举报
文章被收录于专栏:今天你敲代码了吗

说白话:

抖是什么?它啊,就像大炮,投一个炸弹,装一个炸弹。那个函数啊,触发一次就执行一次。

那么,防抖又是什么?就像机关枪,突突突,不管打多少次,打完子弹仓里都要重新装子弹。高频触发函数,时间间隔会重新计算。当在最后一次触发函数时(最后一个子弹打完),时间到达执行一次。

B0003763AC281C21E791E523E80881C2.png

说人话:

事件响应函数在一段规定时间(前/后)才执行。如果在规定时间内,再次触发,重新计算时间。

初模样:

代码语言:javascript
复制
<div class="box"></div>
<button id="btn">取消防抖</button>
<script>
  let obox = document.querySelector('.box')
  let count = 0
  obox.innerHTML = count
  obox.onmousemove = function () {
    obox.innerHTML = count++
    console.log(count);
  }
</script>

当鼠标移动n次,就会触发n次。

整改模样:

代码语言:javascript
复制
// <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script> 
// 或
<script src="https://cdn.jsdelivr.net/npm/underscore@1.13.1/underscore-umd-min.js"></script>
<script>
let obox = document.querySelector('.box')
let count = 0
obox.innerHTML = count
function todo(e) {
  obox.innerHTML = ++count
  console.log(e);
}
obox.onmousemove = _.debounce(todo, 1000)
</script> 

直接使用lodash.js或者underscore.js中的防抖函数,就可以做到1s内,鼠标疯狂移动只触发一次。

2.gif

造个模样

对于我们而言,光知其然,是远远不够的;我们更要知其所以然! 二话不说,咱们就来凭空捏造一个把!

就underscore而言,先剖析这个debounced(防抖动)函数。它有三个参数:防抖动的函数fun、需要延迟的毫秒数wait、是否立即执行immediate。

第一版

先照葫芦画瓢,把形参先整好。最先在鼠标移动时,它接收的是一个函数,所以需要返回一个函数;其次,需要等待规定时间内执行,需要一个定时器。

代码语言:javascript
复制
function debounce(fn, wait = 200, immediate = false) {
  let timer = null
  return function () {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn()
    }, wait)
  }
}

可以使用setTimeout定时器,将功能函数在一定时间内执行一次。这样最基础的防抖函数就🆗拉!

第二版

我们不光需要考虑功能函数,还需要考虑到在执行函数功能时,fn函数中可能使用event事件、内部this指向问题。此外第一版只完成了后执行,我们还需要完成立即执行的功能。

代码语言:javascript
复制
let obox = document.querySelector('.box')
let count = 0
obox.innerHTML = count
function todo(e) {
  obox.innerHTML = ++count
  console.log(this, e);
}
obox.onmousemove = _.debounce(todo, 1000,true)
// <div class="box">1</div>
// MouseEvent{isTruted: true, screenX: 87, screenY: 388, clientX: 68, clientY: 295,...}

在使用我们第一版的this指向的是window,并且e为undefined。 在自定义debounce函数中,我们发现返回的函数this指向div,这时我们就需要在fn函数执行时,改变this指向。

考虑参数传递问题,在返回函数中接收参数,在函数执行时传入参数即可。

代码语言:javascript
复制
function debounce(fn, wait = 200, immediate = false) {
  let timer = null
  return function (...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, wait)
  }
}

此外,我们还需要考虑是否立即实行,及第三个参数。

如果传入的参数immediate为true,那么就执行fn函数;如果为false的话,那就需要在一定时间之后执行(使用setTimeout)。

使用immediate来判断是否立即执行:当立即执行时,此时必须没有定时器,执行函数。等待2s,将定时器清空,等待执行下一次。

代码语言:javascript
复制
function debounce(fn, wait = 200, immediate = false) {
  let timer = null, result
  return function (...args) {
    if (timer) clearTimeout(timer)
    if (immediate) {// 立即执行
      (!timer) && fn.apply(this, args)  // 一开始就执行,无定时
      timer = setTimeout(() => {
        timer = null
      }, wait)
    } else {// 后执行
      timer = setTimeout(() => {
        fn.apply(this, args)
      }, wait)
    }
  }
}

此外还可以通过变量存储,记录执行顺序。

代码语言:javascript
复制
function debounce(fn, wait = 200, immediate = false) {
  let timer = null
  let isEnd = true // 默认后执行
  return function (...args) {
    if (timer) clearTimeout(timer)
    if (immediate) { // 先执行
      isEnd && fn.apply(this, args)
      isEnd = false
    }
    timer = setTimeout(() => {
      (!immediate) && fn.apply(this, args) // 后执行
      isEnd = true
    }, wait)
  }
}

第三版

在第二版的基础上我们可以添加函数返回值和取消抖动的方法。 添加函数返回值,可以记录执行函数的值,不管是立即执行还是后执行,最后统一返回这个值。

代码语言:javascript
复制
function debounce(fn, wait = 200, immediate = false) {
  let timer = null, isEnd = true, result
  let debounced = function (...args) {
    if (timer) clearTimeout(timer)
    if (immediate) {
      isEnd && (result = fn.apply(this, args))
      isEnd = false
    }
    timer = setTimeout(() => {
      (!immediate) && (result = fn.apply(this, args))
      isEnd = true
    }, wait)
    return result
  }
  return debounced
}

使用result记录返回值,最后返回即可。上述代码做了一点点小改动,将整个返回函数使用变量记录,将该变量返回。这样方便于接下来,给函数添加取消抖动的方法。

代码语言:javascript
复制
function debounce(fn, wait = 200, immediate = false) {
  let timer = null, isEnd = true, result
  let debounced = function (...args) {
    if (timer) clearTimeout(timer)
    if (immediate) {
      isEnd && (result = fn.apply(this, args))
      isEnd = false
    }
    timer = setTimeout(() => {
      (!immediate) && (result = fn.apply(this, args))
      isEnd = true
    }, wait)
    return result
  }
  debounced.cancel = function () {
    if (timer) clearTimeout(timer)
    timer = null
  }
  return debounced
}

在cancel方法中,直接清除抖动的定时器,并将该变量回收。

函数返回值异步问题

很感谢读者提的建议,我使用underscore后发现,确实接收的返回值存在异步问题。

代码语言:javascript
复制
let obox = document.querySelector('.box')
let obtn = document.querySelector('#btn')
let count = 0
function todo(e) {
  obox.innerHTML = ++count
  console.log(this, e);
  return count
}
let debounceFn = _.debounce(todo, 1000, false)
obox.onmousemove = (e) => {
  let value = debounceFn(e)
  console.log(value);
}

当我第一次进入div时,执行一次todo函数,此时返回值count应该为1,但是实际输出为undefined。第二次进入的时候,输出为1,但是页面的count为2。返回值返回的是上一个返回值。

为解决异步问题,我们可以使用promise来解决。

代码语言:javascript
复制
function debounce(fn, wait, immediate) {
  let timer = null, result
  let debounced = function (...args) {
    return new Promise(res => {
      if (timer) clearInterval(timer)
      if (immediate) {// 立即执行
        if (!timer) {
          result = fn.apply(this, args)
          res(result)
        }
        timer = setTimeout(() => {
          timer = null
        }, wait);
      } else {
        timer = setTimeout(() => {
          result = fn.apply(this, args)
          res(result)
        }, wait);
      }
    })
  }
  debounced.cancel = function () {
    if (timer) clearTimeout(timer)
    timer = null
  }
  return debounced
}
let obox = document.querySelector('.box')
let obtn = document.querySelector('#btn')
let count = 0
function todo(e) {
  obox.innerHTML = ++count
  console.log(this, e);
  return count
}
let debounceFn = debounce(todo, 1000, false)
obox.onmousemove = async (e) => {
  try {
    let value = await debounceFn(e)
    console.log(value);
  } catch (e) {
    console.log(e);
  }
}

使用promise解决返回值异步问题,在调用时,使用async/await,将其同步。进入div,调用一次,输出值为1,调用两次,输出值为2,返回值同步。

image.png

有什么用

防抖最常见的应用莫过于解决频繁访问接口的问题了。 总结一下常见的应用:

  • 防止表单多次提交
  • 搜索框输入查询(监听输入框输入内容,设定每隔一段时间访问接口)
  • scroll滚动触发
  • 浏览器窗口缩放时,resize事件
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 今天你敲代码了吗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初模样:
  • 整改模样:
  • 造个模样
    • 第一版
      • 第二版
        • 第三版
          • 函数返回值异步问题
      • 有什么用
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档