一、为什么需要防抖与节流?
在前端开发中,滚动事件(scroll)、输入框输入(input)、窗口调整(resize)等高频操作会频繁触发回调函数。若直接绑定原始事件处理器,可能导致:
- 性能浪费(如页面卡顿)
- 接口重复请求(如搜索联想词)
- 无效计算(如连续点击提交按钮)
防抖与节流通过控制函数执行频率,成为优化这类场景的标配方案。
二、防抖(Debounce)详解
1. 核心思想
延迟执行,事件停止触发后等待指定时间才执行,若在等待期间再次触发则重新计时
2. 应用场景
- 搜索框输入联想词(用户停止输入500ms后请求)
- 窗口大小调整结束后的布局计算
- 文本编辑器自动保存(停止编辑后保存)
3. 代码实现解析
export const debounce = (func, delay) => {
let timer;
return function(...args) {
clearTimeout(timer); // 清除前一个定时器
timer = setTimeout(() => { // 重新开始计时
func.apply(this, args);
}, delay);
}
}
实现特点:
- 闭包保存
timer状态 - 每次触发都会重置倒计时
- 最终只执行最后一次触发后的回调
三、节流(Throttle)深度解析
1. 核心思想
稀释执行频率,保证在固定时间间隔内至少执行一次,避免过于频繁的调用
2. 应用场景
- 页面滚动加载更多(每200ms检查一次位置)
- 鼠标移动轨迹记录(游戏中的角色移动)
- 高频点击按钮的提交限制
3. 代码实现解析
export const throttle = (func, delay) => {
let timer, last;
return function(...args) {
let now = +new Date();
if (last && now < last + delay) { // 在冷却期内
clearTimeout(timer);
timer = setTimeout(() => { // 设置延迟执行保证最后一次触发生效
last = now;
func.apply(this, args);
}, delay);
} else { // 超过冷却期立即执行
last = now;
func.apply(this, args);
}
}
}
实现特点:
- 混合使用时间戳与定时器
- 首次触发立即执行
- 确保最后一次触发会被执行
- 实际间隔可能大于
delay(延迟执行阶段)
四、对比与选型指南
| 维度 | 防抖(Debounce) | 节流(Throttle) |
|---|---|---|
| 执行时机 | 停止触发后执行 | 固定间隔执行 |
| 执行次数 | 只执行一次 | 至少执行一次 |
| 场景差异 | 结果导向型(如搜索) | 过程控制型(如滚动) |
| 极端情况 | 持续触发永不执行 | 持续触发按间隔执行 |
选型建议:
- 需要「最终状态」时用防抖(如输入停止后的搜索)
- 需要「过程反馈」时用节流(如滚动进度计算)
五、进阶优化方向
- 可配置化:增加 leading/trailing 选项控制首次/末次是否执行
- 取消功能:添加 cancel() 方法终止等待中的回调
- 返回值处理:Promise 化处理异步操作
- TypeScript 强化:完善类型注解
- 性能监控:添加执行时间统计
总结:理解防抖与节流的本质差异,根据实际场景灵活选择,可显著提升前端应用的性能和用户体验。建议通过 Chrome DevTools 的 Performance 面板验证优化效果。