节流和防抖,都是为了降低请求频率,减少不必要的损耗。
原则:既要实现节流和防抖的功能,还要保证不对事件处理函数本身的执行上下文产生影响。
防抖 (debounce)
短时间大量触发同一事件,只执行一次函数,实现延迟执行。
具体实现:
每次触发一个事件(比如点击按钮),都重新开始计时(重新设置计时器),
直到xx毫秒内没有下一次操作,才执行这个事件的处理程序。
v0:
function debounce(fn) {
return function() {
setTimeout(() => {
fn();
}, 1000)
}
}
const ele = document.getElementById("debounce");
ele.addEventListener("click", debounce(fn));
function fn() {
console.log("clicked");
}
在debounce()函数中返回了一个函数,这个函数用做事件处理程序,它启动一个定时器,
当计时结束时执行传入的函数fn;
这个版本虽然实现了延时执行,但是没有在每次触发后重置定时器,改进如下
function debounce(fn) {
let id = null; // 第一次执行时的id为null
return function() {
clearTimeout(id);
id = setTimeout(() => {
fn();
}, 1000);
}
}
const ele = document.getElementById("debounce");
ele.addEventListener("click", debounce(fn));
function fn() {
console.log("clicked");
}
这样做仍然不完善,因为这里的 fn 作为实际上的事件处理函数,它的
this 指向 window(非严格模式),而不是事件侦听器绑定到的对象;
而且如果向防抖处理后的函数传入参数,fn 将无法接收到。
再改进如下:
function debounce(fn) {
let id = null;
return function() {
clearTimeout(id);
id = setTimeout(() => {
/* 箭头函数没有自己的 this和 arguments */
handleChange.call(this, arguments);
}, 400);
}
}
节流 (throttle)
节流即每隔一个时间间隔,执行一次任务。
按照上面的思路,给出两种写法:
写法1:
这个写法与防抖很相似,区别只在于定时器的处理。
function throttle(fn) {
let timeout = null;
return function() {
if(!timeout) {
timeout = setTimeout(()=>{
timeout = null;
fn.call(this, arguments);
}, 400);
}
}
}
写法2:
function throttle(fn) {
let prev = 0;
return function() {
let now = Date.now();
if( now - prev > 400 ) {
fn.call(this, arguments);
prev = now;
}
}
}