一、问题背景
开发中,经常会遇到以下场景:监听鼠标移动 onmousemove,监听页面滚动 onscroll,监听大小变化 onresize,监听 input 输入等。这些场景下,事件会被频繁触发,但我们并不想事件被频繁触发,这时就需要通过防抖和节流来限制频繁操作。
二、两者区别及实现
防抖和节流都是为了解决事件频繁触发的问题,但在实现原理上有些不同。
防抖函数(debounce)是在短时间内多次触发同一事件时,只执行第一次或最后一次。根据执行时机,可有两种写法。
一段时间内的持续触发,只执行第一次触发操作的写法:
debounceFirst(func, delay) {
//闭包中,timer 是同一个
let timer;
return function() {
// onscroll触发的,实际是这个函数
// 函数执行时才能确认this指向
// 在此例中,方法由window对象调用,所以this指向window
const context = this;
const args = arguments;
// 第一次触发时,timer为undefined,carryOut为true,执行func
// 1s内再次触发时,timer为定时器id,carryOut为false,不执行func
if (timer) {
// clearTimeout后timer的值依然为定时器id,carryOut为false,不执行func
clearTimeout(timer)
};
const carryOut = !timer;
timer = setTimeout(() => {
// 超过设置的延迟时间1s后,将timer赋值为null,carryOut为true,执行func
timer = null;
}, delay)
if (carryOut) {
// 通过 apply 接收 func 的参数
func.apply(context, args);
}
}
}
// 当第一次触发窗口滚动时执行一次function,后面的都不执行;
// 当持续1s没有窗口滚动,1s后再次触发时,再次执行一次function
window.onscroll = debounceFirst(function, 1000);
一段时间内的持续触发,只执行最后一次触发操作的写法:
debounceLast(func, delay) {
let timer;
return function() {
// onscroll触发的,实际是这个函数
const context = this;
const args = arguments;
// 1s内再次触发时,清除定时器
if (timer) {
clearTimeout(timer)
};
// 重新开启定时器,1s后执行func
timer = setTimeout(() => {
func.apply(context, args);
}, delay)
}
}
// 当持续1s没有窗口滚动,才执行一次function
window.onscroll = debounceLast(function, 1000);
由以上两种写法可以看出,若事件持续执行,没有间隔设置的 delay 时长时,可能函数被触发一次后永远不会被触发,甚至一次都不会触发。节流函数可以解决这一问题。
节流函数(throttle)是指连续触发事件在n秒内执行一次,2n秒内执行两次...根据执行时机,同样有两种写法。
n秒内的持续触发,在n秒开始时执行一次。
function throttleStart(func, wait) {
let previous = 0;
return function() {
const now = Date.now();
const context = this;
const args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
// 窗口滚动时function立即执行一次,然后在连续触发中每1s执行一次function
window.onscroll = throttleStart(function, 1000);
n秒内的持续触发,在n秒结束时执行一次。
function throttleEnd(func, wait) {
let timeout;
return function() {
const context = this;
const args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
// 1s内的连续触发在1s结束后,执行一次function
window.onscroll = throttleEnd(function, 1000);