不会。这里要区分创建函数和调用函数。
search(event.currentTarget.value);
只是调用已经创建好的 search 函数,并传入新的参数,不会重新创建 search。search 只在下面这行执行 debounce(...) 时创建一次:
const search = debounce(async (keyword: string) => {
// ...
}, 300);
debounce 内部通过闭包保存同一个 timer:
function debounce<T extends (...args: any[]) => void>(fn: T, wait: number) {
let timer: ReturnType<typeof setTimeout> | undefined;
return function (...args: Parameters<T>) {
if (timer) clearTimeout(timer); // 取消上一次定时任务
timer = setTimeout(() => { // 创建本次定时任务
fn(...args);
}, wait);
};
}
调用过程大致是:
输入 a
→ 创建定时器 1
100ms 后输入 ab
→ 清除定时器 1
→ 创建定时器 2
100ms 后输入 abc
→ 清除定时器 2
→ 创建定时器 3
之后 300ms 没有输入
→ 定时器 3 执行
→ 只请求 abc
所以你的理解有一半是对的:
- 每次输入不会创建新的
search函数; - 每次调用确实会清除旧 timer,再创建一个新的 timer;
- 这正是防抖的核心:不断重新计时,最终只执行最后一次回调【1】【2】。
可以验证 search 始终是同一个函数:
const first = search;
search('a');
search('ab');
search('abc');
console.log(first === search); // true
真正错误的是把 debounce 放进事件回调里:
input.addEventListener('input', (event) => {
// 错误:每次 input 都创建一个新的防抖函数,
// 每个函数都有自己独立的 timer,旧 timer 无法被清除
const search = debounce(request, 300);
search(event.currentTarget.value);
});
这样每次输入都会产生新的闭包和独立的 timer,最终可能每个请求都执行,防抖就失效了。
正确方式就是你原来的写法:先创建一次防抖函数,再反复调用它。
const search = debounce(request, 300);
input.addEventListener('input', (event) => {
search(event.currentTarget.value);
});
如果是在 React 函数组件中,还要避免每次 render 都重新创建防抖函数,通常用 useMemo 或 useRef 保持实例稳定:
const search = useMemo(
() =>
debounce(async (keyword: string) => {
const res = await fetch(
`/api/search?q=${encodeURIComponent(keyword)}`
);
console.log(await res.json());
}, 300),
[]
);
useEffect(() => {
return () => search.cancel();
}, [search]);
核心判断标准只有一个:debounce(...) 应该执行一次,返回的防抖函数可以调用很多次。