函数防抖

最近的练手项目web-terminal中(也就是一个网页终端,可执行一些命令),在按下键盘后会显示可能匹配的命令列表(假设对应的函数是setHintList),这不仅是按下字母按键会触发,按下删除键、tab键都会触发。那就不得不考虑一个问题,如果我们手速太快,那么setHintList就会频繁触发,但我们只需要响应用户最后一次输入的命令即可,虽然在这个小项目中没啥问题,但是由此可以引出一些对于以后大项目的考虑:如何减小这种多次频繁执行函数带来的性能开销问题?那就是函数防抖~

函数防抖是一种优化技术,用来限制某个函数在一定时间内被调用的频率。当事件被触发后,它会等待一段时间,如果在这段时间内再次被触发,那么它会重新开始等待。

下面是一种实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
export function buildDebounce(fn: (...arg: any[]) => any, duration: number = 300) {
let timer = -1;
return function (this: unknown, ...args: any[]) {
if (timer > -1) {
clearTimeout(timer);
}
timer = window.setTimeout(() => {
fn.bind(this)(...args);
timer = -1;
}, duration);
};
}

buildDebounce接受一个函数fn和一个可选的时间间隔duration(默认为 300 毫秒)作为参数,并返回一个新的函数。

  1. 内部使用了一个变量timer来跟踪定时器的引用。初始值为 -1,表示没有定时器在运行。
  2. 返回的函数在被调用时,首先检查timer是否大于 -1。如果是,说明之前已经有一个定时器在运行,此时会调用clearTimeout清除这个定时器,以取消之前可能正在等待执行的函数调用。
  3. 然后,使用window.setTimeout创建一个新的定时器,在duration毫秒后执行传入的函数fn,并通过bind方法确保函数在正确的上下文中执行。
  4. 当定时器执行完函数后,将timer重置为 -1,表示没有定时器在运行。

(this: unknown, ...args: any[])中,this的值是在调用由buildDebounce返回的函数时,根据调用的上下文确定的。也就是当这个返回的函数被调用时,通过保留传入的 this 值,可以确保在最终执行被包裹的函数 fn 时,fn 能够在正确的上下文中执行。

buildDebounce的使用如下:

1
2
3
4
5
6
7
8
9
function printMessage(message) {
console.log(message);
}

const debouncedPrint = buildDebounce(printMessage);

debouncedPrint('Hello');
debouncedPrint('World');
// 如果在 300 毫秒内连续调用 debouncedPrint,只有最后一次调用会在 300 毫秒后执行打印操作。