防抖(Debounce)和节流(Throttle)是两种常用于控制频繁事件触发的技术,它们在处理高频率事件(如键盘输入、窗口调整大小、滚动等)时尤为有用。虽然它们有着相似的目标,但在实现原理和适用场景上有所不同。
防抖
防抖(Debounce)是一种控制频繁事件的技术,它确保在一段时间内多次触发同一事件时,只有最后一次事件处理程序被执行。防抖的核心原理是在事件被触发后,设置一个延迟定时器。如果在定时器执行前再次触发事件,则重置定时器,重新计时。这样,只有在事件停止触发一段时间后,处理函数才会被执行。
防抖就好像我们按下一个按钮后启动一个计时器,如果在计时器结束前再次按下按钮,计时器会重新开始计时,只有等到没有再次按下按钮的情况下,计时器结束后才会执行一次操作。
示例场景:
- 输入搜索框:在用户停止输入一段时间后再发送搜索请求,以避免频繁的请求。
- 窗口调整大小:在用户停止调整窗口大小一段时间后再执行某些操作。
节流
节流(Throttle)是一种控制频繁事件的技术,它确保在一段时间内只允许某个函数执行一次。节流的核心原理是在第一次触发事件时立即执行处理函数,然后在规定的时间间隔内忽略随后的事件,直到时间间隔结束后再允许执行下一次事件处理。
节流就好像我们玩的FPS游戏,像那种单发的枪,不管我们鼠标按得有多快,它规定时间内也只能开一枪。
示例场景:
- 页面滚动:限制滚动事件的触发频率,每隔100毫秒执行一次事件处理函数,从而减轻浏览器的渲染压力。
- 按钮点击:防止按钮在短时间内被多次点击,限制按钮点击的频率
再形象点可以如下图表示: 防抖和节流虽然看似相似,但它们的应用场景和实现原理有所不同。接下来,我们将深入探讨这两种技术的具体实现方法、适用场景以及如何在实际开发中灵活运用它们。
代码实现
防抖
function debounce(func, delay) {
return function (args) {
clearTimeout(func.id);
func.id = setTimeout(() => {
func(args);
}, delay);
};
}
- 闭包和定时器:
debounce
函数返回的是一个闭包,这个闭包保存了func
和delay
的引用,并且能够访问func.id
,这是一个用于跟踪当前setTimeout
调用的标识符。每次闭包被调用时,它会首先清除当前的定时器(如果有),然后重新设置一个新的定时器。 - 清除定时器:
clearTimeout(func.id);
这行代码是防抖的关键。每当闭包函数被调用时,它都会取消之前设置的定时器。这意味着只要在delay
时间内有任何新的事件触发,之前的定时器就会被取消,从而防止了在delay
时间内多次执行func
。 - 执行时机: 只有在最后一次事件触发后的
delay
时间内没有新的事件发生,func
才会被执行。这是因为每次事件触发都会重置定时器,而func
只会在定时器最终未被清除的情况下执行。
拿出整个代码来看看效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖</title>
</head>
<body>
<div>
没有防抖的input <input type="text" id="inputA">
</div>
<div>
有防抖的input <input type="text" id="inputB">
</div>
<script>
const inputa = document.getElementById("inputA");
const inputb = document.getElementById("inputB");
const ajax = (content) => {
const currentTime = new Date().toLocaleTimeString();
console.log(`[${currentTime}] ajax request: ` + content);
};
function debounce(func, delay) {
return function (args) {
clearTimeout(func.id);
func.id = setTimeout(() => {
func(args);
}, delay);
};
}
inputa.addEventListener('keyup',(e)=>{
ajax(e.target.value);
})
let debounceFunc=debounce(ajax,1000)
inputb.addEventListener('keyup',(e)=>{
let value=e.target.value;
debounceFunc(value)
})
</script>
</body>
</html>
我们模仿一个ajax请求返回时间和内容:
节流
const throttle=(func,delay)=>{
// last 记录上一次是啥时候执行的
// deferTimer 定时器id
let last,deferTimer // 自由变量
// keyup return func 调用时都能找到闭包中的自由变量
return (args)=>{
// 当前时间, 隐式类型转换
let now=+new Date();
if(last&&now-last<delay){
clearTimeout(deferTimer)
deferTimer=setTimeout(()=>{
last=now
func(args)
},delay)
}
else{
last=now // 第一次时间
func(args) // 先执行一次
}
}
}
这段代码实现节流的主要就是:
时间检查:
- 获取当前时间戳
now
。 - 如果自上一次执行以来的时间小于
delay
,则不允许立即执行func
。 - 如果允许执行,更新
last
为当前时间戳并执行func
。
延迟执行:
- 如果当前时间与上一次执行时间的差小于
delay
,则清除现有的deferTimer
(如果存在),并设置一个新的setTimeout
。 - 新的
setTimeout
将在delay
毫秒后执行func
并更新last
。
最后的执行: 如果用户停止触发事件(如停止按键),并且最后一个事件触发后的时间超过了 delay
,那么在该事件之后不会立即有另一个事件来触发执行。但是,由于我们在延迟执行时设置了 setTimeout
,所以即使没有新的触发事件,func
也会在 delay
时间后执行一次,确保了在一定时间内至少执行一次。
我们也给出全部代码来看看实现效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>节流</title>
</head>
<body>
<div class="row">
<div>
没有节流的input <input type="text" id="inputa" />
</div>
<div>
节流后的input <input type="text" id="inputc" />
</div>
</div>
<script>
const inputA = document.getElementById('inputa');
const inputC = document.getElementById('inputc');
const ajax = (content) => {
const currentTime = new Date().toLocaleTimeString();
console.log(`[${currentTime}] ajax request: ` + content);
};
const throttle = (func, delay) => {
let last, deferTimer
return (args) => {
let now = +new Date();
if (last && now - last < delay) {
clearTimeout(deferTimer)
deferTimer = setTimeout(() => {
last = now
func(args)
}, delay)
}
else {
last = now
func(args)
}
}
}
inputA.addEventListener('keyup', (e) => {
ajax(e.target.value);
})
let throttledFunc = throttle(ajax, 500)
inputC.addEventListener('keyup', (e) => {
let value = e.target.value;
throttledFunc(value)
})
</script>
</body>
</html>
总结
防抖和节流在控制高频事件方面各有优势:
- 防抖:适用于那些需要在一段时间内不触发事件后执行操作的场景,例如搜索输入框、窗口调整等。它通过重置定时器,确保事件处理程序只在最后一次触发后的延迟时间内执行。
- 节流:适用于那些需要控制函数执行频率的场景,例如滚动事件、按钮点击等。它通过设置时间间隔,确保在规定的时间间隔内函数只执行一次。
两者都可以保证最后一次触发的事件可以正常执行;