防抖适合用户操作有收尾意图的场景,如搜索输入完成、窗口调整结束,核心是等待停顿后执行最后一次;节流适用于需稳定频率响应的场景,如滚动计算可视区、拖拽更新位置,本质是固定间隔最多执行一次。

防抖和节流不是“万能优化”,它们只在特定场景下有效——比如高频事件(resize、scroll、input)触发的重复计算或请求。用错地方反而引入延迟或丢失逻辑,先明确这点再动手。
防抖(debounce)适合什么场景?
用户操作有“收尾意图”的时候才执行,比如搜索框输入完成、窗口调整结束。核心是「等停顿」。
-
debounce会忽略中间所有调用,只保留最后一次触发后的延迟执行 - 典型错误:给按钮点击加防抖——用户点一下没反应,体验崩坏
- 常见漏掉的参数:
immediate控制是否立即执行第一次(比如希望输入即查,但后续停顿才查),不传默认为false - 示例中别直接写
setTimeout闭包嵌套三层,用函数工厂更清晰:
function debounce(fn, delay, immediate = false) {
let timer = null;
return function(...args) {
const callNow = immediate && !timer;
clearTimeout(timer);
if (callNow) fn.apply(this, args);
timer = setTimeout(() => {
if (!immediate) fn.apply(this, args);
timer = null;
}, delay);
};
}
节流(throttle)什么时候必须用?
需要“稳定频率”响应,而不是“等停顿”。比如滚动时实时计算可视区域、拖拽时更新位置、Canvas 动画帧同步。
- 节流本质是「固定间隔最多执行一次」,有两种主流实现:定时器版(
setTimeout)和时间戳版(Date.now()) - 定时器版可能丢帧(最后一次触发若没到间隔就被清空,就不执行了);时间戳版更可靠,但首次调用立刻执行
- 别把节流当防抖用:比如给
input加节流,用户快速打字会明显卡顿或漏字符 - 注意 this 和参数透传——箭头函数会丢失
this,必须用fn.apply(this, args)
为什么原生事件监听里直接写逻辑会出问题?
浏览器对 scroll 或 resize 的触发频率极高(每秒几十次),每次触发都执行 DOM 查询、计算、重排,CPU 占用飙升,页面直接卡死。
立即学习“Java免费学习笔记(深入)”;
- 没包装的
addEventListener('scroll', handleScroll)是性能黑洞,尤其在移动端 - Chrome DevTools 的 Performance 面板里能看到大量
Layout和Recalculate Style堆积,这就是典型信号 - 防抖/节流只是第一层——如果
handleScroll本身还在反复查offsetTop或getBoundingClientRect(),照样慢 - 进阶做法:配合
IntersectionObserver替代 scroll 监听可见性,或用requestIdleCallback把非关键计算延后
容易被忽略的兼容性和边界问题
防抖节流函数看似简单,但上线后常因环境差异翻车。
- IE 不支持
...args扩展运算符,需用arguments兼容(或直接上 Babel) - 移动端
touchmove触发频率比scroll还高,但某些安卓 WebView 里preventDefault会影响节流行为 - 函数被多次绑定(比如组件重复挂载)会导致多个独立 timer,互相干扰——务必确保实例唯一,或手动
cancel - React 中若在函数组件内定义防抖函数且未用
useCallback缓存,每次渲染都会新建,防抖失效
真正难的不是写对防抖节流,而是判断该用哪个、用在哪、以及它后面那层逻辑是否也经得起高频考验。









