防抖(debounce)和节流(throttle)
连续触发(触发频率很高)的时间,不进行优化,会出现页面卡顿现象。
常见的需要优化的事件:
鼠标事件:
- mousemove(拖拽)
- mouseover(划过)
- mouseWheel(滚屏)
键盘事件:
- keydown(按下键盘)
- keypress(按下字符键盘)
- keyup(弹起键盘)
window resize/scroll
- DOM 元素动态定位
优化方式是控制事件处理器在一段时间内的执行次。
防抖
频繁(连续)触发事件(比如用户触发输入事件input
),不执行目标动作,当不在触发事件了,再执行。
实现思路,在事件处理器内,使用 setTimeout 包裹目标动作,一直触发事件,就清除上次的定时器,不再触发触发事件,会执行最后一个定时器,目标动作也执行一次了。
<input type="text" id="input" />
JS代码:
function debounce(callback, delay) {
let timeout = 0;
return e => {
console.log('清除', timeout, new Date());
clearTimeout(timeout); //input 一直触发,就清除上一次的定时器,防止执行目标函数,直到事件不触发事件,最后一个定时器没有清除,delay 时间后就会执定时器,就确保了目标函数只执行一次。
timeout = setTimeout(() => {
callback(e);
}, delay);
console.log('新的', timeout, e.target.value, new Date());
};
}
let print = debounce(e => {
let value = e.target.value;
console.log(value, new Date());
}, 1000);
document
.querySelector('#input')
.addEventListener('input', print, false);
清除定时器的时机很关键,在新定时器生成之前,如果在之后,会将所有定时器都清除,目标函数一次都不执行。
节流
防抖是多次触发事件,目标函数只执行一次,不管触发这些事件用了多少时间。而节流是在一段时间内,确保目标函数只执行一次,实现缓慢执行目标函数的效果。
上面的输入使用节流实现:
let thorttle = (callback, delay) => {
let timeout = 0;
let now = new Date() - 0;
return e => {
console.log('now', now);
let last = new Date() - 0;
clearTimeout(timeout);
if (last - now >= delay) {
console.log('时间间隔', last - now);
callback(e);
now = last;//将上执行的时间赋值给 now
} else {
//将 delay 时间内多次触发事件,目标函数合并到这里执行
timeout = setTimeout(() => {
callback(e);
}, delay);
}
};
};
let write = thorttle(e => {
console.log(e.target.value, new Date());
}, 5000);
document
.querySelector('#input')
.addEventListener('input', write, false);
两者比较
节流在某个时间段内,目标函数能执行一次,限制目标函数的执行频率,不管事件触发了多少次;
防抖是多次触发事件,目标函数只执行一次,不管触发了这些事件用了多少时间。
节流函数限制目标函数的执行频率,有连续变化的效果,适用于关注变化过程的操作,可以调整目标函数执行频率使得变化更加平滑,比如动画、改变窗口时执行某些操作等,常用事件resize
、scroll
、mouseWheel
、touchmove
、mouseover
等;
防抖函数适用于更关注结果的操作,不太关注操作过程,常见的事件有 input
、keyup
等。
最后看一个 将 防抖 和 节流都用 resize
事件的效果,更能体会两者的区别:
function debounce(callback, delay) {
let timeout = 0;
return e => {
clearTimeout(timeout);
timeout = setTimeout(() => {
callback(e);
}, delay);
};
}
let print = debounce(e => {
let value = e.target.value;
console.log('debounce', window.innerWidth);
}, 500);
let thorttle = (callback, delay) => {
let timeout = 0;
let now = new Date() - 0;
return e => {
let last = new Date() - 0;
clearTimeout(timeout);
if (last - now >= delay) {
callback(e);
now = last;
} else {
timeout = setTimeout(() => {
callback(e);
}, delay);
}
};
};
let write = thorttle(e => {
console.log('thorttle', window.innerWidth);
}, 500);
window.addEventListener('resize', write, false);
window.addEventListener('resize', print, false);