背景

防抖和节流是前端开发中常用的两种技术,用于解决事件处理和资源请求等方面的性能问题。在前端开发中,由于事件和资源的处理需要时间,因此防抖和节流可以有效地减少不必要的并发和处理时间,提高页面性能和响应速度。举个例子,滚动事件的触发频率是很高的,假如你想在滚动时执行一个函数,防抖和节流可以控制函数触发的频率,可以极大的提高页面性能和响应速度。

什么是防抖(debounce) ?

防抖是一种事件处理技术,用于限制事件处理程序的执行次数,它通过在事件触发后延迟执行事件处理程序,以避免重复执行。防抖常常用于处理频繁触发的事件,例如点击、滚动等。(这句话不够准确,请继续往下看)

想象一下你在电梯里,电梯正好要关门了,突然又有一个人过来,电梯再次打开门等待人上电梯,电梯并不会立马关门换层,而是延迟了它的功能(在楼层之间移动),但优化了资源

自己试试吧。在按钮上方单击或移动鼠标:

你可能会发现鼠标一直在点击或者移动的话,目标事件就不会触发,因为它一直在延迟,为什么函数不立即执行,使其表现得与原始的非防抖函数完全一样呢,然后后续的防抖阈值时间内触发的函数就不再执行。

下面就是一个立即执行的示例

debounce-leading

防抖实现

延迟执行版

function debounce(func, timeout = 500) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
};
}

立即执行版

function debounce(func, timeout = 500) {
let timer;
return function (...args) {
clearTimeout(timer);
if (!timer) func.apply(this, args);
timer = setTimeout(() => {
timer = null;
}, timeout);
};
}

合并版

function debounce(func, wait = 500, immediate = false) {
let timeout;
return function (...args) {
clearTimeout(timeout);
if (immediate && !timeout) func.apply(this, args);
timeout = setTimeout(function () {
timeout = null;
if (!immediate) func.apply(this, args);
}, wait);
};
}

什么是节流 (throttle) ?

节流(throttle)是一种限制函数调用频率的技术。和防抖类似,节流也是为了优化性能,避免某些函数被频繁调用,导致页面卡顿或者浏览器崩溃。

和防抖之间的主要区别是节流保证定期执行函数,至少每X毫秒执行一次。

一个常见的例子,一个可以向下无限滚动的页面(例如小红书),需要检查用户离底部还有多远,如果接近底部,需要发送请求获取后续的内容,如果这里使用防抖的话就不合适了。

节流实现

时间戳版

function throttle(func, wait = 500) {
let previous = 0;
return function (...args) {
var now = Date.now();
if (now - previous > wait) {
func.apply(this, args);
previous = now;
}
};
}

定时器版

function throttle(func, wait = 500) {
let timeout;
return function (...args) {
if (!timeout) {
timeout = setTimeout(function () {
timeout = null;
func.apply(this, args);
}, wait);
}
};
}

所以比较两个方法:

第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件

双剑合璧版
这个实现方式与基本版的实现非常相似,但是增加了一个计时器,用于更加精确地控制时间间隔。具体来说,当函数被调用时,它会记录上次执行的时间和当前时间的差值,然后根据差值来判断是否需要立即执行函数。如果时间间隔大于指定的时间间隔,那么立即执行函数并更新上次执行的时间;否则,通过计时器延迟执行函数并更新上次执行的时间。

这种实现方式可以更加精确地控制函数执行的时间间隔,避免出现累计误差。但是需要注意的是,这种实现方式可能会导致函数被延迟执行,具体取决于计时器的精度和浏览器的性能等因素

function throttle(func, interval = 500) {
let lastTime = 0;
let timer;
return function(...args){
const currentTime = new Date().getTime();
const timeDiff = currentTime - lastTime;
clearTimeout(timer);
if (timeDiff >= interval) {
func.apply(this, args);
lastTime = currentTime;
} else {
timer = setTimeout(() => {
func.apply(this, args);
lastTime = new Date().getTime();
}, interval - timeDiff);
}
};
}

参考链接