最初接触到函数的防抖与节流是在lodash库中看到了这样一个函数throttle,当时的心理感受是mmp,这是啥,一查翻译,哦...原来,还是不懂。防抖与节流看着很高级,其实在日常的编程中也是经常遇到,值得我们拥有。
函数防抖(debounce)
当事件持续被触发时,并不立即执行事件处理函数,而是等到约定的时间后再执行,当约定的时间到来之前,又一次触发了事件则重新进行计时。
- 函数防抖的简单实现
//arguments 为非箭头函数中可用的内部变量,为传递给函数参数的伪数组。
const debounce = function(fn, wait) {
let timer = null;
return function handler() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(function() {
fn.apply(context, args);
}, wait)
}
}
通过 fn.apply 调用 fn 是为了能够保持函数 handler 调用者的 this 上下文,而不是指向 window。来看下面的例子:
function calcCirleArea(arg) {
const r = this.r; // 10
const area = 3.14 * r * r; // 314
return area;
}
const calcArea = debounce(calcCirleArea, 1000);
const circle = {
r: 10,
calcArea
};
circle.calcArea();
执行 circle.calcArea(),在函数 calcCirleArea 中可以访问到对象 circle 的属性 r,原因就是通过 fn.apply 调用时绑定了 circle 的上下文。假如我们通过fn()直接调用,在非严格模式下 calcCirleArea 函数 this 的指向为 window,this.r为 undefined,所以需要通过 fn.apply 来调用。
-
防抖函数应用场景
比如在注册用户的时候,验证密码是否符合规范,我们并不需要在用户输入时频繁地去验证,而是等到用户最后一次触发输入后,等待一定时间没有再输入,此时认为输入已经结束,可以进行验证了,这是符合逻辑的,也提升了性能。
无防抖效果:
防抖效果:
函数节流(throttle)
当事件持续被触发时,保证函数在一定时间内只执行一次事件处理函数。
-
函数节流的简单实现
函数节流的实现主要有时间轴的方法和计时器的方法。时间轴的方法是通过比较事件触发时间与上一次函数执行时间(第一次为 0,保证第一次一定执行)的差 dt 来判断是否执行事件处理函数,如果 dt 大于约定的时间则执行,反之则不执行;计时器的方法是在事件触发时,如果当前没有计时器则设置计时器来触发事件处理函数的执行。这两种方法对于最后一次触发事件,都有可能不会执行。
- 时间轴
const throttle = function(fn, wait) { //pre设置为0使得第一次一定执行 let pre = 0; return function() { const now = Date.now(); const context = this; const args = arguments; if (now - pre > wait) { fn.apply(context, args); pre = now; } } }- 计时器
const throttle = function(fn, wait) { let timer = null; return function() { const context = this; const args = arguments; clearTimeout(timer); if (!timer) { timer = setTimeout(function() { fn.apply(context, args); timer = null; }, wait) } } }
有些时候我们希望至少第一次和最后一次触发事件得到响应,这就可以结合时间轴和计时器的方法。 如下,当事件最后一次触发时要么达到了约定的时间可以立即执行事件处理函数,要么设置一个 timer 等待约定的时间后执行。
const throttle = function(fn, wait) {
let pre = 0;
let timer = null;
return function() {
const context = this;
const args = arguments;
const now = Date.now();
clearTimeout(timer);
const dt = now - pre;
if (dt > wait) {
fn.apply(context, args);
pre = now;
} else {
timer = setTimeout(function() {
fn.apply(context, args);
}, wait)
}
}
}
-
节流函数应用场景
比如在客户端搜索,服务器返回搜索结果时我们希望尽快刷新搜索结果,但又不希望频繁的向服务器请求结果而导致性能下降,节流就是一个很好的选择。
无节流效果:

节流效果:

总结
- 防抖和节流都是用来控制函数的频繁触发,提升性能。
- 在频繁触发事件的情况下防抖有可能不执行处理函数,而节流在事件触发后,一定会执行一次。
- 为了能够在第一次和最后一次触发事件时执行处理函数,可以结合时间轴和计时器的方法。