小说几句 debounce 和 throttle (防抖和节流)

251 阅读5分钟

它的唯一目的就是节省资源,像网络请求,页面渲染。

原理就是将 网络请求,页面渲染放入计时器通过特殊条件判断是否执行(所以它们不会立即执行),从而减少它们的触发

触发:表示响应交互事件调用的 function,并不会立即执行,会进入一个计时器等待执行

执行:表示执行 function 后回去做的业务逻辑,发送请求,重新渲染 UI

连续触发:表示在非常短的时间内连续触发 fn

假设每当用户触发一个交互事件回去调用一个 function,我们称之为 fn

触发时机

  • 防抖(debounce)

    连续触发 1 ... 100 次 fn,执行的是第 100 次

  • 节流 (throttle)

    连续触发 1 ... 100 次 fn,执行的是第 1 次

时间限制

假设每当用户在等待交互事件的最低忍耐等待时间为 1000 ms(1s),我们称之为 delay

  • 防抖(debounce)

    连续触发 1 ... 100 次 fn,当触发第 1 次的时候进入 daley 计时器,计时器结束,如果没有第 2 次触发就执行第 1 次 fn 但是连续触发了 100 次 fn,每次触发都会重置 delay,直到 delay 结束没有新的触发,才会执行 第 100 次触发的 fn

  • 节流 (throttle) 触发 1 ... 100 次 fn,立即执行 第 1 次 触发 fn,如果有第 2 触发,那么并不会执行,而是忽略 虽然连续触发了 100 次,但是只有第一次触发的 fn 会被执行,当 delay 时间之后,新触发的第 1 次就会执行

其他的一些解释

这是一点补充,我看过太多用其他例子来举例的。

上车来举例

比如一辆公交车在车站等人上车

防抖

司机在最后一个人上车以后等待 delay 时间之后,没人上车才走

节流

司机看到第一个上车的人,就立马走,后面不管来多少人都不管了,但是车开的非常快 dalay 时间后又开了回来

moba 游戏举例

防抖

回城就是防抖,只要你动了无数次,也只有最后一次在 delay 时间(回城时间)完成之前不动,才能成功

节流

放技能就是节流,因为不论你怎么点也要等 cd 完成,cd 就是 delay

本质上这些都是再说前面说的两个事情

  • 触发时机
  • delay 时间

使用场景

前面说了这么多都在说他们的区别

那他们的使用场景是什么?

最开始就说了它们的目的,就是节省资源

比如

  • 减少网络请求
  • 减少页面渲染

减少网络请求

比如 搜索

每当我们在 input 输入多个字符的时候,我们并不需要每个字符都触发网络请求,一般用户都会在打完完整的字词之后才能表达真正的意思 所以我们需要等待 delay 时间之后才触发网络请求,这个我们需要是用 debounce

减少页面渲染

这个来自我真实的业务需求

首先先说一个说烂的例子

但鼠标移动的时候去重新渲染页面的某个复杂组件,滚一下就重新渲染一下,显然不是一个好方法。

因为这个组件很复杂,连续多次渲染会让用户看到明显的掉帧,卡顿。

这个时候我们使用 防抖(debounce) 还是 节流(throttle)?

当然是节流,节流会在每个 delay 时间都执行一次

可以在每个 delay 时间发生变化,也就是用户体验都能第一时间得到反馈(节流,连续触发,只执行第一次)

但是我认为,如果不需要这种,那么防抖也是可以的。

对了,真实的例子是什么?

比如我有一个可编辑,并且数据量特别大的表格

里面的组件多如牛毛。

可编辑的每个格子,编辑的格子又有 select,inputnumber,checkbox

数据量大,我们使用虚拟列表即可。

最重要的是,因为有些数据内容很长,所以必须要支持表头拉伸(拖拉),方便用户看到这一列的内容

但是频繁的拖拉也会重新渲染表格,非常消耗性能。

别忘了,里面的组件是可编辑的单元格

所以我们需要节流,在 delay 时间给第一时间给出反馈,但是这个 delay 时间必须很短,因为虚拟列表已经降低了非常多的性能消耗,所以每一次拖拉在 800ms 就能有一个不错的反馈。

当然体验还是不如 excel 表格,所以纯 html 来做还是直接给定一条拖拉的线,线表示你要拖拉的位置,而不是真实表头在被拖动,当用户放开鼠标的时候,才发生拉伸表头的效果,也是 防抖 的另类表达

原理实现

防抖(debounce)

function debounce(cb, delay = 250) {
  let timeout;

  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      cb(...args);
    }, delay);
  };
}

节流 (throttle)

function throttle(cb, delay = 250) {
  let shouldWait = false;

  return (...args) => {
    if (shouldWait) return;

    cb(...args);
    shouldWait = true;
    setTimeout(() => {
      shouldWait = false;
    }, delay);
  };
}

以上都是简单的,注意看返回的函数是箭头函数也就是不能绑定 this,所以可以修改成非具名函数,这些在这篇文章里面不是很重要,但是你要知道这个细节