防抖与节流是事件响应策略:防抖只执行最后一次触发(如搜索框停顿后请求),节流则按固定间隔执行(如滚动中每200ms检查位置),二者均用于避免高频无意义调用,而非直接优化CPU或内存性能。

防抖和节流不是“性能优化技巧”,而是「事件响应策略」
它们不直接提升 CPU 或内存性能,而是防止函数被高频、无意义地调用——比如用户狂输 10 个字,input 事件触发 10 次,你却发了 10 次搜索请求;或者拖拽窗口时 resize 每毫秒触发几十次,你却反复重算 DOM 尺寸。真正拖慢页面的,是这些本可跳过的逻辑执行,而非事件本身。
防抖:debounce(fn, delay) —— 只信“最后一击”
每次触发都清掉旧定时器,只在停止触发 delay 毫秒后执行一次。适合“结果比过程重要”的场景:
- 搜索框联想:用户打完 “react” 停顿 300ms 后才查,中间删改都不发请求
- 表单校验(邮箱格式):输入中不提示,光标移出或停顿时再验
- 按钮防重复提交:点击后立刻禁用 + 防抖,避免双击触发两次 API
⚠️ 容易踩的坑:
-
debounce返回的是新函数,不能在addEventListener里每次写debounce(handleInput, 300),否则每次渲染都新建闭包,clearTimeout失效 → 必须提前定义并复用:const debouncedInput = debounce(handleInput, 300) - 没传
this和参数:原始事件回调里的this是 DOM 元素,但防抖函数里会丢失 → 必须用fn.apply(this, arguments)或展开参数 -
delay设太小(如 50ms)≈ 没防抖;设太大(如 800ms)用户会明显感知延迟 → 搜索建议推荐 200–300ms,表单校验可设 400–500ms
节流:throttle(fn, interval) —— 要“节奏感”,不要“全漏掉”
保证函数至少每 interval 毫秒执行一次,不管触发多密。适合“需要感知过程但不能太密”的交互:
立即学习“Java免费学习笔记(深入)”;
-
scroll监听吸顶或懒加载:滚动中每 200ms 检查一次 scrollTop,既不卡顿也不错过关键位置 - 鼠标拖拽更新坐标:canvas 绘图、自定义 slider,不需要每帧都算,16ms(60fps)或 50ms 更合理
-
mousemove实时预览:避免鼠标划过时疯狂重绘缩略图
⚠️ 容易踩的坑:
- 时间戳版(推荐)首次立即执行,但若需“首次也等间隔”,得用定时器版(带
leading/trailing控制) - 别把
interval设成 0 或负数 —— 会导致无限循环或不执行 - 在 Vue/React 中,别在
render或useEffect里动态生成节流函数,同样会破坏引用稳定性 → 应提至组件外或用useCallback缓存
选错就等于交互逻辑错:防抖 vs 节流,本质是业务语义问题
不是“哪个更快”,而是“你要响应什么”:
- 用户 resize 窗口:用防抖 —— 等他拖完再重排布局,稳
- 用户 scroll 页面想实时显示“已滚动高度百分比”:用节流 —— 防抖会卡住不动,直到他停手,体验断层
- 游戏里按空格射击:用节流(
interval=200ms)限制射速,不是防抖 —— 防抖会让连按变单发,节流才能实现“每 200ms 最多一发”的节奏
真实项目中,lodash 的 _.debounce 和 _.throttle 已处理取消、立即执行、this 绑定等边界,比手写更可靠;但前提是理解它们为何这样设计 —— 否则连配置项都看不懂,比如 leading: true 是啥意思,maxWait 解决什么问题。











