防抖是将多次连续触发合并为最后一次执行,适用于输入搜索、窗口resize等需等待停顿的场景;节流则固定间隔最多执行一次,适用于scroll、mousemove等需周期性响应的场景。

防抖(debounce)是什么?什么时候该用?
防抖是指把多次连续触发变成最后一次触发执行,中间的触发全部被忽略。典型场景是用户在搜索框输入时实时请求接口——你不想每按一个键就发一次请求,而是等他停顿 300ms 后再查。
关键点在于:debounce 不是“限制频率”,而是“重置计时器”。只要事件持续触发,定时器就不断被清除并重建。
- 适合输入校验、窗口 resize 后重新计算布局(等用户彻底拖完)
- 不适用于需要及时响应的操作,比如鼠标移动轨迹记录
- 首次触发是否立即执行,取决于实现里有没有
immediate参数
function debounce(func, wait, immediate = false) {
let timeout;
return function(...args) {
const later = () => {
timeout = null;
if (!immediate) func(...args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func(...args);
};
}
节流(throttle)是什么?和防抖的根本区别在哪?
节流是固定时间间隔内最多执行一次函数,不管触发多少次。比如监听 scroll 事件做懒加载,你希望每 100ms 最多检查一次滚动位置,而不是卡死主线程。
核心差异:防抖关注“停顿后执行”,节流关注“周期性执行”。节流更强调“保底响应”,防抖更强调“收敛结果”。
立即学习“Java免费学习笔记(深入)”;
- 适合 scroll、mouseMove、drag 等高频但需反馈的场景
- 要注意时间窗口起点:是第一次触发就开始计时(leading),还是等第一次执行完才开始(trailing)
- 部分实现会同时支持 leading + trailing,但两次执行间隔可能小于设定值
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
为什么不用 setTimeout 或 setInterval 直接写?
直接裸用定时器容易漏掉边界情况:比如组件卸载后定时器还在跑,导致 func 执行时访问已销毁的 DOM 或 state;或者没清理上一次的 timeoutId,造成内存泄漏或逻辑错乱。
标准封装的 debounce / throttle 函数都隐含了状态管理与清理机制。你自己手写时必须注意:
- 每次调用前
clearTimeout已存在的句柄 - 在组件 unmount 或事件解绑时手动清理(React 中可用
useEffect返回清理函数) - 避免闭包中引用过期的 props/state(必要时用
useRef缓存最新值)
性能优化效果真的明显吗?
不是“用了就快”,而是“避免了不必要的重复工作”。比如一个 scroll 事件在 1 秒内触发 200 次,不做节流,你的回调就执行 200 次;加了 throttle(fn, 100),最多执行 10 次;换成 debounce(fn, 100),可能只执行 1 次(如果用户一直滚动)。
但要注意副作用:节流可能让动画卡顿,防抖可能让操作“延迟感”太强。实际中常根据 UX 需求微调 wait 值,比如输入搜索设为 300ms,resize 设为 16ms(接近一帧)。
真正容易被忽略的是:防抖/节流本身也有开销,频繁创建闭包、操作定时器,在极端高频场景下反而成为瓶颈。这时候要考虑用 requestIdleCallback 或 Web Worker 卸载逻辑。











