防抖适用于用户操作停止后再执行,如搜索框输入;节流适用于固定频率执行且需及时响应,如滚动监听。二者均需处理this绑定、参数传递及定时器清理,推荐使用Lodash或React自定义Hook。
防抖和节流不是 JavaScript 语言层面的必需机制,而是应对高频事件(比如 resize、scroll、input)导致函数被过度调用的实际需求。不加控制时,一个快速拖拽滚动条的动作可能触发上百次 scroll 回调,直接卡死 UI 或浪费计算资源。
典型例子是搜索框的实时建议:用户每敲一个字就发请求,既浪费带宽又让后端压力陡增;改成防抖后,只在用户停顿(比如 300ms)后再发起最后一次查询。
debounce 的核心是每次触发都清除前一次定时器,重置倒计时immediate = true 可让第一次触发立刻运行,后续触发则重新计时func 而不处理上下文,回调里 this 会丢失,推荐用 func.apply(context, args)
function debounce(func, wait, immediate = false) {
let timeout;
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);
};
}
比如 canvas 绘图跟随鼠标移动,或监听页面滚动位置做吸顶判断。这类逻辑不需要每帧都响应,但也不能等到用户停下才动——否则体验会明显滞后。
throttle 常见实现有「定时器版」和「时间戳版」:前者更稳定,后者更省资源但首次触发可能延迟context 和 args,否则多次触发时参数会错乱function throttle(func, wait) {
let timeout = null;
return function(...args) {
if (!timeout) {
timeout = setTimeout(() => {
func.apply(this, args);
timeout = null;
}, wait);
}
};
}
手写容易漏掉边界情况:比如未清理定时器导致内存泄漏、未处理 this 绑定、未支持取消(cancel 方法)、未兼容服务端渲染(SSR 中无 setTimeout)。
_.debounce 和 _.throttle 支持 leading/trailing、maxWait 等配置,且自带 cancel 和 flush
useDebounce 或 useThrottle 自定义 Hook,避免在组件重渲染时重复创建防抖函数watch 本身不防抖,需配合 lodash/debounce 或 async-validator 类库手动包裹真正容易被忽略的是「该用哪个」——不是看技术实现多优雅,而是看业务语义:用户输入后要不要等停顿?滚动过程中要不要及时反馈?这两个问题的答案,直接决定该选防抖还是节流,而不是反过来根据函数怎么写来凑场景。