每日一个npm包 —— _.debounce

284 阅读3分钟

debounce 背景

在前端开发中会遇到一些频繁的事件触发,比如:

  • button 点击事件
  • input 输入事件

但有的时候,我们不想这些事件绑定的回调被频繁执行,否则可能出现表单重复提交,或者页面卡顿等情况导致降低用户体验感。为了解决这个问题,我们可以给事件的回调加上防抖 debounce

debounce 原理

经过 debounce 处理后的回调逻辑是:你尽管触发事件,但是我一定在事件触发 n 秒后才执行回调,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行回调。

6cg0byl3kez4b0isjlum.gif 带 debounce 的 autocomplete:用户打字时一直触发输入事件,但只会在用户停止打字后一段时间后才请求数据。若每输入一个字符就请求数据,会带来很多不必要的请求,同时也增加渲染负担。(此图来自:danilorivera95.medium.com/react-autoc…

现实生活的例子就是电梯的关门时机。电梯总是在最后一个上电梯的人进入后,再等一段时间才关门。如果这段时间内,又有人进来了,那么电梯会重新开始计时,再等相同的时间才关门。

debounce 实现

基础实现

根据上述描述,我们可以实现如下代码。注意 functhis 绑定和入参 args 这两个小细节。

function debounce(func, wait) {
  let timeout = null;

  function debounced(...args) {
    // 取消上次定时器回调的执行
    clearTimeout(timeout);

    // 重新开始计时,n 秒后执行 func
    timeout = setTimeout(() => {
      func.call(this, ...args);
    }, wait);
  }

  return debounced;
}

支持立即调用

underscore 源码提供的 debounce 有第三个入参 immediate

  • immediatefalse:在事件触发 n 秒后,才执行 func
  • immediatetrue:立即执行 func,等到停止触发 n 秒后,才可以重新触发执行

Untitled.png

图中 at_begain 即 immediate。(此图来自 benalman.com/projects/jq…

function debounce(func, wait, immediate) {
  let timeout = null;

  function debounced(...args) {
    // 保存 func 的执行结果
    let res;

    timeout && clearTimeout(timeout);

    // 利用 timeout 是否为 null 来判断事件是否为连续事件中的第一个事件
    if (!timeout && immediate) {
      // 当 func 被立即执行的时候,可以让 debounced 返回 func 的执行结果
      res = func.call(this, ...args);
    }

    timeout = setTimeout(() => {
      if (!immediate) func.call(this, ...args);
      timeout = null;
    }, wait);

    return res;
  }

  return debounced;
}

支持取消

underscore 中的 debounce 还支持取消。比如有一个 debounce 的时间间隔是 10 秒钟,immediatetrue,这样的话,我只有等 10 秒后才能重新触发事件,现在我希望有一个按钮,点击后,取消防抖,这样我再去触发,就可以又立刻执行。

只需要给 debounced 挂一个 cancel 函数即可:

function debounce(func, wait, immediate) {
  let timeout = null;

  function debounced(...args) { ... }

  debounced.cancel = function () {
    clearTimeout(timeout);
    timeout = null;
  };

  return debounced;
}

debounce 源码

underscore 中源码的实现思路和上述代码一致,不过上面的代码中每次执行 debounced 时,总会清空旧的定时器再生成一个新的。underscore 则使用了追踪两次 debounced 调用时间间隔的思路优化了上述问题,有兴趣的读者们可以去 github 仓库中看看。

引用资料&延申阅读