防抖和节流是防止事件失控的必要手段,用于应对scroll、input等高频事件导致的渲染卡顿、接口过载等问题;防抖适用于“用户停手后执行”,如搜索联想;节流适用于“定期执行”,如滚动加载。

防抖和节流不是“可选优化”,而是防止事件失控的必要手段
用户快速滚动、频繁点击或实时搜索时,scroll、input、resize 等事件会密集触发。不加控制的话,函数可能在 1 秒内执行几十次——UI 渲染卡顿、接口被刷爆、内存泄漏都可能由此而来。防抖(debounce)和节流(throttle)不是锦上添花,是守住响应边界的底线措施。
防抖适用于“等用户停下来再干活”的场景
典型如搜索框输入联想、窗口尺寸校验、表单提交按钮防连点。核心逻辑是:每次触发都重置计时器,只保留最后一次调用。
常见错误是把 setTimeout 写死在函数体里却不清理前一个定时器:
function debounce(fn, delay) {
return function() {
setTimeout(() => fn.apply(this, arguments), delay); // ❌ 每次都新建,旧的没清除
};
}
正确做法是缓存并清除上一个 timer:
- 用闭包保存
timer变量 - 每次执行前先
clearTimeout(timer) - 支持立即执行(leading edge)需额外判断是否首次触发
节流适用于“必须定期响应,但不能太密”的场景
比如滚动监听加载更多、鼠标拖拽位置上报、Canvas 动画帧同步。它保证函数至少间隔 delay 时间执行一次,而不是等用户停手。
立即学习“Java免费学习笔记(深入)”;
两种主流实现方式差异明显:
-
时间戳版:记录上次执行时间,当前时间减去上次 >
delay才执行 —— 首次立刻触发,末次可能被跳过 -
定时器版:用
setTimeout控制下一次可执行时机 —— 首次延迟触发,末次更稳定
别混淆它们的触发节奏;选错会导致交互反馈滞后或丢失关键状态。
别直接手写,小心 this、arguments 和取消逻辑
原生 JS 手写容易漏掉几个关键点:
-
this指向丢失:必须用fn.call(this, ...args)或箭头函数绑定上下文 -
arguments在严格模式或箭头函数中不可用,建议用剩余参数...args - 没有提供
cancel()方法,导致组件卸载时定时器仍在运行(内存泄漏) - 未考虑
leading/trailing组合行为,比如防抖开启 leading 后又想禁用 trailing,逻辑易出错
业务代码里优先用 lodash.debounce 或 lodash.throttle,它们已处理好边界情况;若需轻量替代,推荐 use-debounce(React)或封装带 cancel 的版本。










