Debounce & Throttle in JavaScript
有些事件——比如 scroll
或者 keypress
——发生的频率较高,频繁的触发回调函数可能会造成一些性能上的问题。
比如根据滚动条位置调整某个元素的位置。每次scroll
事件的发生都会引起元素位置的变动,不仅不必要,有时还会形成视觉效果上的“抖动”。
解决办法只能是限制回调函数执行的频率。有两种思路
- 限定执行函数的间隔事件,即所谓的 "throttle", 限流的意思。
- 极端一点,一直等到状态稳定后再执行函数。即 "debounce",去抖动的意思。
setTimeout()
首先是需要可以延后执行代码的功能。在 JavaScript 中,setTimeout()
用于这个功能。
setTimeout(function(){
console.log("3 seconds later");
}, 3000)
clearTimeout()
clearTimeout()
用于取消先前计划的 setTimeout()
Debounce 去抖动
以监听窗口的 scroll 事件为例,这个事件会频繁地触发。如果每次触发时都需要进行耗时地运算,就会带来性能问题。
但连续地 scroll 事件不会一直发生,会在某个时刻“稳定”下来。这有点像弹簧,在外力作用下会一直晃动,但最终会稳定静止下来。
Debounce 的意思是去抖动,方法是等到状态稳定的时候再进行相应的动作。这需要解决两个问题:
- 延迟动作的执行
- 判断抖动是否停止
“延迟”可以通过 setTimeout()
做到。判断抖动停止不是很容易做到,
但可以反过来考虑,如果抖动仍然继续,可以取消先前延迟的动作计划,重新计划动作的发生。
window.addEvenetListener('scroll', onScroll);
function onScroll(){
let timer;
clearTimeout(timer);
timer = setTimeout(function(){
...
}, 500);
}
上面的例子是有问题的,timer
这个变量作为局部变量不能在多次调用之间保存状态。
在 JavaScript 里可以通过返回函数的方式绑定一个变量,这个变量存在于返回的函数外部,因而可以在多次调用之间保存状态。
function debounce(callback, delay){
let timer = null;
return function(){
clearTimeout(timer);
timer = setTimeout(callback, delay);
}
}
window.addEvenetListener('scroll', debounce(function(){
... the task ...
}, 500));
Throttle 限流
在上面 debounce()
的基础上,可以容易得出 throttle()
的实现。
function throttle(callback, delay){
let timer = 0;
return function(){
let now = Date.now();
if( (now - timer) < delay ) return;
timer = now;
setTimeout(callback, delay);
}
}