手写防抖函数核心是「延迟执行且每次触发重置定时器」;基础版支持leading(首次立即执行)和trailing(结束延迟执行),实用版仅保留trailing逻辑,需注意this指向、cleartimeout时机及闭包隔离。

手写防抖函数,核心是「延迟执行,且每次触发都重置定时器」。只要用户还在频繁操作(比如输入、滚动),就不断清除上一个定时器,只保留最后一次操作后的延迟执行。
基础实现(带立即执行可选)
下面是一个兼顾常用场景的防抖函数,支持是否立即执行(leading)和是否在结束时执行(trailing):
function debounce(func, wait, options = {}) {
const { leading = false, trailing = true } = options;
let timer = null;
return function(...args) {
const later = () => {
timer = null;
if (trailing) func.apply(this, args);
};
const callNow = leading && !timer;
if (timer) clearTimeout(timer);
timer = setTimeout(later, wait);
if (callNow) func.apply(this, args);
};
}
说明:
- leading: true → 第一次触发立刻执行,后续连续触发不执行,直到停止触发后 wait 毫秒才恢复可触发状态
- trailing: true(默认)→ 停止触发 wait 毫秒后执行最后一次调用
- 两者都为 true 时,首次立即执行,末次延迟执行(一般不用,易重复)
- 两者都为 false,函数永远不会执行(无意义,慎用)
最简实用版(推荐日常用)
多数场景只需「等用户停下手再执行」,比如搜索框输入、窗口 resize 处理:
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
使用示例:
const handleInput = debounce(() => {
console.log('发送搜索请求');
}, 300);
input.addEventListener('input', handleInput);
注意点和常见坑
-
this 指向:用
func.apply(this, args)保证原函数的this和参数正确传递 -
清空定时器时机:必须在
setTimeout前调用clearTimeout,否则旧定时器可能残留 -
闭包变量隔离:每个 debounce 实例都有独立的
timeout,互不影响 -
取消防抖:如需手动取消(比如组件卸载),可返回一个
cancel方法(进阶扩展)
可选:加 cancel 方法(用于清理)
适合 React useEffect 或 Vue onUnmounted 中手动清除:
function debounce(func, wait) {
let timeout;
const debounced = function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
debounced.cancel = () => {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
不复杂但容易忽略细节,抓住「清旧启新」这个核心逻辑,就能稳稳写出可用的防抖函数。











