防抖是将连续多次触发压缩为最后一次触发后的延迟执行,解决高频操作导致的性能问题;核心是每次触发重置定时器,停止触发超时后才执行函数。
防抖(debounce)不是节流,也不是简单的延时执行——它是把「连续多次触发」压缩成「最后一次触发后的延迟执行」。典型场景是用户在 in 框中快速打字、
putresize 窗口、scroll 滚动时发起搜索或重绘,若不控制,可能一秒触发几十次函数,造成卡顿或多余请求。
核心逻辑:每次触发都重置定时器;只有停止触发超过指定时间后,才真正执行目标函数。
不能只用 setTimeout + clearTimeout 就完事。常见坑包括:this 指向丢失、参数无法透传、返回值被忽略、未提供取消能力。
func.apply(context, args) 保留原始调用上下文和参数cancel 方法(比如用户中途离开页面要清资源)function debounce(func, wait, immediate = false) {
let timeout = null;
return function(...args) {
const later = () => {
timeout = null;
if (!immediate) func.apply(this, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(this, args);
};
}
// 使用示例
const handleInput = debounce((e) => {
console.log('搜索:', e.target.value);
}, 300);
input.addEventListener('input', handleInput);
防抖适合「等用户停下来再响应」的场景,比如搜索建议、表单校验、窗口尺寸适配;节流(throttle)适合「保证固定频率执行」的场景,比如拖拽位置上报、Canvas 绘图帧率控制。
wait 毫秒最多执行一次,不管中间触发多少次useCallback + 依赖数组使用,避免每次渲染都新建防抖函数防抖不是加个包装函数就万事大吉。真实环境里几个关键点常被跳过:
timeout 可能成为内存泄漏源,尤其在组件卸载时需手动 cancel()
input 在某些 Android 浏览器中会延迟触发,单纯防抖可能漏掉首次输入,需结合 compositionstart/compositionend 处理中文输入法最麻烦的不是写不对,而是忘了它只管「执行时机」,不管「执行结果」——比如防抖后的请求失败了,得靠单独的错误重试逻辑兜底,这和防抖本身无关。