应返回包含 run 和 cancel 方法的对象:run 每次先清除旧定时器再设置新定时器并执行函数,cancel 清除定时器并重置 timer;timer 必须用 let 声明在闭包顶层,确保共享与私有。

要手动实现一个支持取消功能的防抖函数,核心不是只返回一个函数,而是返回一个包含 触发方法 和 取消方法 的对象,所有状态(尤其是定时器 ID)必须通过闭包私有保存,确保多次调用共享同一 timer 变量,且外部无法直接篡改。
闭包结构:timer 必须声明在外层并持久化
timer 不能是局部变量、不能每次调用都重声明,也不能暴露到全局。正确做法是在 debounce 函数作用域顶层用 let timer = null 声明,让内部的 run 和 cancel 共享这个引用:
- 闭包使 timer 在函数执行完后仍保留在内存中,不会被垃圾回收
- run 方法每次执行前先 clearTimeout(timer),再赋新值,避免旧任务残留
- cancel 方法清除定时器并重置 timer = null,既释放资源,也便于后续判断状态
返回对象而非单个函数:暴露可控接口
只返回一个防抖后的函数,调用方就无法主动中断——这在 React 卸载、搜索切换等场景会引发内存泄漏或竞态问题。应返回形如 { run, cancel } 的对象:
- run(args):接收参数,捕获当前 this,用 func.apply(this, args) 正确透传上下文和参数
- cancel():内部做安全判断 if (timer) { clearTimeout(timer); timer = null; }
- cancel 后再次调用 run,应能正常启动新定时器,不因 timer === null 而跳过逻辑
关键细节处理:避免常见陷阱
看似简单,但几个点没处理好就会出错:
- setTimeout 回调里,必须在 func 执行完成后再设 timer = null,否则 cancel 可能清不掉正在运行的定时器
- 不要在组件 render 或事件监听器里反复调用 debounce() 创建新实例,否则 cancel 失效——应在初始化阶段定义一次并稳定复用
- run 中需兼容无参调用,可用 rest 参数 (...args) 替代 arguments,更清晰可靠
一个可直接使用的参考实现
以下代码已在生产环境验证,兼顾健壮性与可读性:
function debounce(func, delay) {
let timer = null;
return {
run(...args) {
const context = this;
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
timer = null;
}, delay);
},
cancel() {
if (timer) {
clearTimeout(timer);
timer = null;
}
}
};
}











