一. 防抖和节流的作用:
防止高频的事件触发造成不必要的计算消耗, 比如, 典型的应用场景:
- 搜索框 input事件;
- 鼠标移动 mousemove事件;
- 视窗大小变化 resize事件;
二. 防抖的优缺点:
创建timer, 当新的触发出现时, 销毁老的timer.
性能消耗稍大于节流(不过, 这点性能差别算不了什么).
如果连续的调用间隔一直小于delay, 会造成长时间不触发.
三. 节流的优缺点:
通过时间差来判断要不要放弃当次触发,
不需要创建和销毁timer.
但是因为根据前一次的时间差来判断放弃, 可能会扔掉最后一次.
如果最后一次包含重要的状态切换, 则可能丢失.
四. 防抖+节流的升级版
在防抖的基础上增加一个值, 用于记住上次的触发时间,
采用上次触发时间到当前时间的差, 用于计算真实的delay大小.
当delay小到等于0的时候, clearTimeout就没有机会清掉前一次触发了.
五. 代码实现和测验:
function debounce(cb, delay) { // 防抖(回调, 延迟)
let timer = null; // timer对象
return function(...args) { // 返回闭包给外部调用, 收集外部调用的参数
if(timer !== null) clearTimeout(timer); // 先清除前一次timer
timer = setTimeout(() => { // 设置delay timer
cb && cb.apply(this, args); // 将外部参数传给回调
}, delay);
}
}
function throttle(cb, delay){ // 节流(回调, 延迟)
let lastTick = 0; // 上次发送时间点
return function(...args) { // 返回闭包函数, 取到外部参数
if (Date.now() - lastTick > delay) { // 时间差大于delay, 才调用fn
lastTick = Date.now(); // 更新时间
cb && cb.apply(this, args); // 将外部参数传给回调
}
}
}
function debounce_throttle(cb, delay) { // 防抖+节流(回调, 延迟)
let timer = null; // timer对象
let lastTick = Date.now(); // 上次发送时间
return function(...args) { // 返回闭包给外部调用, 收集外部调用的参数
if(timer !== null) clearTimeout(timer); // 先清除前一次timer
timer = setTimeout(() => { // 设置delay timer
lastTick = Date.now(); // 更新时间
cb && cb.apply(this, args); // 将外部参数传给回调
}, Math.max(0, delay - (Date.now() - lastTick))); // delay减去已消耗时间
}
}
function A(a) {
this.a = a;
this.proc = function(...args) {
console.log(`[${Date.now()/1000}] A.proc, this:${this.a}, args:${JSON.stringify(args)}`);
}
}
let aa = new A(123);
let bb = new A(456);
let cc = new A(789);
let db = debounce(aa.proc.bind(aa), 1000);
let tt = throttle(bb.proc.bind(bb), 1000);
let dt = debounce_throttle(cc.proc.bind(cc), 1000);
let startTick = Date.now();
let trigger = setInterval(()=>{
console.log(`[${Date.now()/1000}] interval call`);
db(1,2,3);
tt(4,5,6);
dt(7,8,9);
if (Date.now() - startTick > 4000) clearInterval(trigger);
}, 200);
六. 测验输出结果:
> node .\test.js
[1616485874.114] interval call
[1616485874.121] A.proc, this:456, args:[4,5,6]
[1616485874.315] interval call
[1616485874.516] interval call
[1616485874.716] interval call
[1616485874.914] A.proc, this:789, args:[7,8,9]
[1616485874.917] interval call
[1616485875.117] interval call
[1616485875.317] interval call
[1616485875.318] A.proc, this:456, args:[4,5,6]
[1616485875.518] interval call
[1616485875.717] interval call
[1616485875.914] A.proc, this:789, args:[7,8,9]
[1616485875.919] interval call
[1616485876.118] interval call
[1616485876.319] interval call
[1616485876.319] A.proc, this:456, args:[4,5,6]
[1616485876.519] interval call
[1616485876.72] interval call
[1616485876.914] A.proc, this:789, args:[7,8,9]
[1616485876.92] interval call
[1616485877.121] interval call
[1616485877.32] interval call
[1616485877.321] A.proc, this:456, args:[4,5,6]
[1616485877.521] interval call
[1616485877.722] interval call
[1616485877.915] A.proc, this:789, args:[7,8,9]
[1616485877.923] interval call
[1616485878.915] A.proc, this:789, args:[7,8,9]
[1616485878.937] A.proc, this:123, args:[1,2,3]
>
从结果看, 防抖因为连续触发的间隔小于设定的delay, 所以直到最后才获得触发机会.
节流前期正常触发, 遗落了最后一次触发.
只有第三个版本, 即能均匀触发, 又不丢失最后一次触发.