基础理解 - 防抖与节流

270 阅读4分钟

是什么?

防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

节流:每隔一段时间,只执行一次函数。

防抖和节流图解:demo.nimius.net/debounce_th…

为什么?

为了解决前端工作中一些场景性能问题:在浏览器中,有一些事件(滚动,窗口大小变化)触发的频率过高,或者有些用户可能疯狂点击某个按钮或者在输入框里面疯狂输入(可能是网太慢很着急或者一些其他原因);如果每一次变化、点击和输入都触发事件,就会不停占用资源可能到溢栈等一些缺陷的常胜,然后导致用户体验变差或者功能直接死掉的情况。就出现了防抖和节流的方式来解决这些问题。

防抖和节流通过控制这些事件在一段时间内调用的频率,从而使资源合理分配和少牺牲用户体验。

如何实现?

防抖 - debounce

定义:防抖是在事件触发 n 秒后在执行回调函数,如果在这 n 秒内又被触发,重新计时。

触发 n 秒后,首先需要一个计时器,用来让 n 秒后触发执行;

执行回调函数,这时候需要把传入的参数给到回调函数;

执行完回调函数,需要返回回调函数的结果;

防抖是给一个函数增加一个功能,防抖功能,当目标函数需要的时候运行触发。

所以防抖返回的是一个增加了新功能的函数。

// 基础版本
function debounce(fn, delay) {
  let timer; // 计时器
  
  const debounceFn = function () { // 加上了功能的函数
    const context = this; // 存储当前 this
    
    if (timer) clearTimeout(timer); // n 秒内重新触发,清除计时器归零
    timer = setTimeout(() => { // n 秒后执行
        return fn.apply(context, arguments); // 执行回调函数
    }, delay)
  }

return debounceFn; // 返回加了功能的函数
}

有了基础版本,可以在上面进行优化和提供更多功能,如:

  • 增加取消功能
// 取消功能,我们返回的是函数变量,怎么办?那就在返回的函数变量上新增一个方法取消

function debounce(fn, delay) {
  let timer;
  
  const debounceFn = function () {
    const context = this;
  
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      return fn.apply(context, arguments);
    }, delay);
  }
  
  // 新增的取消在这里
  debounceFn.cancel = function () {
    clearInterval(timer);
    timer = null;
  }

  return debounceFn;
}

还可以增加更多功能,如

  • 增加立即调用功能 flush
  • ...

更多可以参考 lodash 的防抖功能lodash 的实现

节流 - throttle

定义:每隔一段时间,只执行一次函数。

重要的是在一个事件频繁触发的情况下,固定一段事件间隔执行一次函数;

给一个函数事件新增一个节流的功能,我们的函数签名输入是回调函数和间隔时间,输出为给回调函数赋予了节流功能的新函数;

实现思路:当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

function throttle(fn, interval) {
  let timer;
  const throttleFn = function() {
    const context = this;
    let result;
    if (!timer) {
      timer = setTimeout(() => {
        timer = null;
        result = fn.apply(context, arguments)
      })
    }

    return result;
  }
}

可增加更多的功能:

  • 取消功能,同防抖一样;
  • ...

更多可以参考 lodash 的节流功能lodash 的节流实现

使用场景

不用硬记使用场景,理解了概念和几个实例场景之后会在脑子留下了一个钩子,当自己重新进入相应的场景会唤起自己的大脑,会想到是不是可以做一个这样的优化。以下为一些常见的使用实例场景:

  1. 防抖
    • Input输入的搜索或验证(输入后需要进行后台搜索或验证)
  2. 节流
    • DOM 元素的拖拽功能实现(mousemove)
    • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
    • 计算鼠标移动的距离(mousemove)
    • Canvas 模拟画板功能(mousemove)
    • 搜索联想(keyup)

注意

  • 使用防抖的时候避免绑定错误
// 错误
$(window).on('scroll', function() {
   _.debounce(doSomething, 300); 
});
// 正确
$(window).on('scroll', _.debounce(doSomething, 200));