JS中的防抖(debouncing)与节流(throttling)是用来控制一个函数在一定时间内执行的次数(频次),他俩个用处相近、但又不完全相同。
注:文中出现的图都是通过此网站操作的截图,读者可以通过该网站上可视化的demo边阅读边尝试,加深印象。
出现原因
为什么会出现这俩个技巧呢?换句话说,为什么要控制函数执行的频次?我们看下面的动图,当我们在区域内鼠标移动时,如果监听移动直接执行函数的时候,函数在1s内被执行过很多次。
如果我们在回调函数中做大量运算或Dom操作,函数如此高的执行频次就会造成页面的卡顿。为了避免这种情况,防抖与节流就起到了至关作用。
防抖函数
当在一段时间内事件被连续调用时,防抖函数会控制这段时间内函数只会被真正执行一次。被真正执行的时机又可以分成延时结束后和延时开始前俩个阶段,使用频率比较高的是延时结束后阶段被执行,我们可以通过下面这张图看出来:一段时间内的连续点击,加入防抖技巧函数只会被执行一次,并且是在不再触发之后的一段时间后被执行。
防抖函数的原理:就是当函数被触发时通过setTimout设置一个定时器来延时去执行真正的函数,当函数在这段时间内再次被触发,则重置定时器,从而做到一段时间内只被真正执行一次(文末有具体实现)。
这种延时结束后执行的防抖函数使用的场景如下:
- 当用户输入内容进行实时搜索时,为了避免大量无用请求,可以使用防抖函数,当输入停止时进行真正请求。
- 监听窗口改变时,我们的诉求只是需要计算最终的窗口大小即可,所以可以监听resize时,对回调函数进行防抖控制。
那延时开始前执行的防抖函数是用于什么场景呢,我们思考下这种场景:当用户频繁点击功能按钮时,短时间内的结果无变化时,那我们就需要尽可能快的响应用户操作,而不是等用户停止操作时,才去响应。具体延时开始前执行的效果如下图所示。可以看到我们只是将真正执行的时机提到第一次,整体频次还是被控制到一次。
节流函数
节流函数与防抖函数的区别在于:节流函数会设置一个最长等待执行时间,也就是说节流控制函数在一定时间内一定会被执行一次。
如果我们通过监听scroll去做一些滚动相关的事情,我们真正的诉求是适当的控制频次,而不是控制执行一次。如果我们使用了防抖对滚动函数进行控制,就会发现只有当停止滚动时才会被触发,这是不满足需求的。可以通过下图看到节流对函数控制频次的效果。
你也可以通过这个链接来看对于触底加载功能,分别使用防抖和节流进行控制的效果,其中很明显,左侧通过防抖只有触底无法滚动时才会被触发,右侧通过节流控制体验上要更好些。
实现
// 防抖
function debounce(fn, delay) {
let timer = null;
return function(...args) {
const context = this;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, delay);
}
}
// 节流
function throttle(fn, delay) {
let prev = Date.now();
return function (...args) {
const context = this;
const now = Date.now();
if (now - prev >= delay) {
fn.apply(context, args);
prev = Date.now();
}
};
}