- 在开发中我们经常会遇到事件频繁回调导致的性能和体验下降的问题,比如滚动事件回调或者输入框根据用户输入实时返回sug的场景。
- 一方面用户并不需要那么精确的实时,另一方面实时的操作会发送不必要的多余请求,给服务端造成压力,也给端上造成困扰。
throttle和debounce就是为了解决这类问题出现的
节流 throttle
限制函数的执行频率,保证一定时间内只执行一次函数调用。无论触发频率多高,都会在指定时间间隔内执行一次函数。
举个例子
假设我们在排队进电影院,进入电影院的规则是 每10分钟进一波人
第一个人2:00来了,他需要2:10才能进,第二个人2:02来了,他也需要2:10才能进,第三个人2:09来了,他也需要2:10才能进。第四个人2:11来了,他需要在2:20才能进
场景
用于处理连续触发的事件,比如滚动事件、鼠标移动事件等。控制函数的执行频率,以减少资源消耗和提高性能。
实现示例
js
// delay表示延迟的时间间隔(单位毫秒),callback是需要进行防抖的函数
function throttle(delay, callback) {
let timeoutID;
let lastExec = 0;
function wrapper() {
// 🌟🌟🌟通过 `self` 变量临时保存 `this` 的值,从而在 `exec` 函数中通过 `callback.apply(self, args)` 传入正确的 `this` 值
const self = this;
// 计算距离最近一次函数执行后经过的时间 `elapsed`
const elapsed = Number(new Date()) - lastExec;
const args = arguments;
function exec() {
// 更新最近一次函数的执行时间
lastExec = Number(new Date());
callback.apply(self, args);
}
// 并清除之前设置的计时器
clearTimeout(timeoutID);
// 如果经过的时间大于设置的时间间隔 `delay`,那么立即执行函数
if (elapsed > delay) {
exec();
} else {
// 如果经过的时间小于设置的时间间隔 `delay`,那么通过 `setTimeout` 设置一个计数器,让函数在 `delay - elapsed` 时间后执行
timeoutID = setTimeout(exec, delay - elapsed);
}
}
return wrapper;
}
使用示例
js
// 实际做事的函数
function foo() { console.log('foo..'); }
// 对foo函数进行节流,返回新的函数
const fooWrapper = throttle(200, foo);
for (let i = 1; i < 10; i++) {
// 使用节流函数,限制每200ms执行一次
setTimeout(fooWrapper, i * 30);
}
// => foo 执行了三次
// => foo..
// => foo..
// => foo..
这两句代码是非常有必要的 const self = this; const args = arguments;
我们知道在JS非箭头函数中的this并不是定义时的上下文,而是执行时的上下文,而我们的debounce把原来的函数做了一层包装会导致执行时this的改变。这里的两行代码就巧妙的解决了这个问题
防抖 debounce
在事件被触发n秒后去执行回调函数。如果n秒内该事件被重新触发则重新计时。结果就是将频繁触发的事件合并为一次,且在最后执行
举个例子
假设我们在排队进电影院,进入电影院的规则是 最后一个人来了之后等10分钟没有人再来才可以进去,否则大家一起等。
第一个人来了,他需要等10分钟看有没有其他人来,如果10分钟内第二个人来了,那么第一个人和第二个人需要重新开始等10分钟看有没有其他人来,同样的道理,如果10分钟内第三个人来了,那么第一个人、第二个人、第三个人需要重新开始等10分钟看有没有其他人来,依次类推。假设第5个人来了,这五个人等了10分钟后没有人再来,他们就可以进影院了
场景
用于处理频繁触发的事件,比如窗口大小调整、搜索框输入等。避免函数执行过多次,以减少网络开销和性能负担
注意事项
我们从debounce的原理可以知道,如果时间间隔很长事件可能永远不会触发。所以我们使用的时候这个时间设置的不宜太长
实现示例
js
// `debounce` 函数可以借助 `throttle` 函数实现
function debounce(delay, callback) {
// 保存超时id
let timeoutID;
function wrapper() {
// 🌟🌟🌟通过 `self` 变量临时保存 `this` 的值,从而在 `exec` 函数中通过 `callback.apply(self, args)` 传入正确的 `this` 值
const self = this;
const args = arguments;
function exec() {
callback.apply(self, args);
}
// 每次清空定时器,并重新设置定时器
// 这里就做到了在规定时间内多次函数调用把前一个取消的效果
clearTimeout(timeoutID);
timeoutID = setTimeout(exec, delay);
}
return wrapper;
}
使用示例
js
function bar() { console.log('bar..'); }
const barWrapper = debounce(200, bar);
for (let i = 1; i < 10; i++) {
setTimeout(barWrapper, i * 30);
}
// => bar 执行了一次
// => bar..