前端的节流和防抖
5月 16, 2021
工作中排查数据问题发现了前端按钮可能会触发两次点击的情况,这个问题除了要在后端做幂等以外,正好最近我也在做一些前端的工作,也就专门研究了下前端的解决方案。本来要解决的是防抖问题,但是查了些资料发现一般都是防抖和节流一起,它们解决方案相似但又不完全相同,下面就分别介绍下方便对比使用。
防抖(debounce) #
问题描述 #
防抖很容易理解,有时如果鼠标连按,那么在很短的时间内就会触发两次点击,从而触发了两次请求。要解决这个问题,就需要保证按了鼠标的一段时间内的再次点按不能生效,或者两次点按只能生效一次。由于可能还会再连按三次,所以解决方案还是让前一次点按失效,让最后一次的点按生效。
所以防抖的解决方案是,按钮点击的 n 秒后再执行,如果 n 秒内再次触发了点击,就重新计时,通过这样的方式来保证点击的 n 秒后函数只被执行一次。
解决方案 #
根据上面的思路,可以维护一个计时器变量,下面是一个简单实现:
var timer;
function debounce(fn, delay) {
clearTimeout(timer);
timer = setTimeout(function(){
fn();
}, delay);
}
这里使用了 setTimeout
和 clearTimeout
内置函数来实现。其中 setTimeout
的返回值是一个正整数,表示定时器的编号,这个值可以传递给 clearTimeout()
来取消该定时器。
在使用这个函数时,将 debounce
包含在实际函数外面即可:
function doSomething() {
console.log('mounse moved');
}
document.onmousemove = () => {
debounce(doSomething, 1000);
}
Lodash 也提供了相关的实现,可以直接使用:
jQuery(window).on('resize', _.debounce(doSomething, 150));
节流(throttle) #
问题描述 #
节流在后端的场景里很常见:当请求的并发量超出设置的流量控制策略时,只能有一部分流量能进入实际业务,其余流量被过滤掉。我之前也介绍过几个流量控制策略,前端里的流量控制策略没有那么复杂,基本就是固定窗口算法(计数法)。
所以节流要解决的问题是:每隔 n 秒的时间只执行一次函数。
解决方案 #
同样是利用 setTimeout
来定义一个定时器,在 delay
毫秒以后进行函数调用同时设置 timer = null
,下一次函数调用就根据 timer
是否为空来判断是否进行限流。
var timer;
function throttle(fn, delay) {
return function () {
var _this = this;
var args = arguments;
if (timer) {
return;
}
timer = setTimeout(function () {
fn.apply(_this, args);
timer = null;
}, delay)
}
}
使用起来和 debounce
一样:
function doSomething() {
console.log('mounse moved');
}
document.onmousemove = () => {
throttle(doSomething, 1000);
}
Lodash 同样提供了方案,可以直接使用:
jQuery(window).on('scroll', _.throttle(doSomething, 100));
Lodash 为节流和防抖都提供了 cancel
方法用于取消调用,非常完善,所以绝大部分情况下还是推荐直接用 Lodash 了。
节流和防抖的区别 #
下面这个图很直观的反映了节流和防抖的区别:防抖把一段时间内的触发取最后一次按生效进行调用,基本上是期望合并后的调用产生的结果是幂等的;而节流是在每一个时间段内取一个生效的调用,多数情况下是期望每次调用的结果会不一样,所以也可以根据自己想要的结果进行选择。
防抖适用场景 #
按钮,按钮一般期望只会点一次。
窗口 resize,调整窗口大小一般也是期望拿到最终调整后的结果。
节流适用场景 #
滚动加载,减少刷新列表的次数,优化体验和性能。
搜索联想,减少因不断拼写而产生的联想次数,优化体验。