防抖是让一个函数在「连续触发」时,只执行最后一次。比如用户在搜索框里打字,你不想每按一次键就发一次请求,而是等他停顿 300ms 后再查——这就是典型的防抖场景。
关键判断点:你关心的是“操作结束后的结果”,而不是“过程中发生了什么”。常见于输入校验、窗口 resize 后重排布局、按钮重复点击拦截。
debounce 函数内部通常用 setTimeout + clearTimeout 实现leading 选项的版本function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}节流是限制函数「固定时间间隔内最多执行一次」。比如监听鼠标滚轮或拖拽事件,你想每 100ms 最多处理一次,避免高频回调压垮主线程。
核心差异:防抖会“攒”调用,节流会“匀速放行”。节流不关心你停不停,只要到了间隔时间点,就执行一次(哪怕中间触发了十次)。
throttle 默认启用 traili
ng,即保证最后一次触发也会被执行,这点容易被忽略function throttle(fn, limit) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= limit) {
fn.apply(this, args);
lastCall = now;
}
};
}直接在组件方法里套 debounce 或 throttle 很容易出问题:组件卸载后定时器还在跑,this 指向丢失,甚至引发内存泄漏。
beforeDestroy 钩子中手动清除定时器(如果用了闭包保存 timer 引用)setup 中,用 onBeforeUnmount 清理;或者直接用 lodash-es 的 debounce 并配合 ref 存储实例useEffect 清理,且不能把防抖函数写在 render 内部(否则每次渲染都新建,失去防抖意义);推荐封装成自定义 Hook,如 useDebounceCallback
最常踩的不是逻辑错,而是上下文和生命周期没管住。
debounce 写在 React 的 onClick 里:onClick={debounce(handleClick, 300)} —— 每次渲染都新建防抖函数,等于没防methods 里直接返回 debounce 结果,但没绑定 this,导致内部 fn.apply(this) 的 this 是 undefined
16ms),在 60fps 下几乎没效果;设太长(如 1000ms)又卡顿,需结合业务节奏调,输入类一般 200–400ms,滚动类 50–100ms
防抖和节流不是万能胶,它们解决的是“调用频率失控”,而不是“逻辑错误”或“性能差”。用之前,先打开 DevTools 的 Performance 面板录一段操作,看瓶颈真正在哪。