JavaScript 防抖与节流:实现、差异与实战场景
## 一、为什么需要防抖和节流
页面上有很多高频触发的事件:窗口 `resize`、搜索框 `input`、页面 `scroll`、按钮重复点击。这些事件每秒钟可能触发几十上百次,如果每次都执行完整逻辑,会带来两个后果:
- **性能浪费**:频繁的计算或 DOM 操作导致掉帧,滚动和输入明显卡顿;
- **业务副作用**:搜索接口被连续调用几十次、表单被重复提交、埋点数据重复上报。
防抖(debounce)和节流(throttle)就是用来控制执行频率的两种经典手段。它们的核心目标一致——降低执行次数,但控制策略完全不同。
## 二、防抖:等安静下来再执行
防抖的思路是:事件触发后不立即执行,而是等待一段固定时间;如果在这段时间内事件又被触发,就重新计时。只有当事件停止触发超过设定时间后,才真正执行一次。
```javascript
function debounce(fn, delay = 300) {
let timer = null;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
};
}
```
使用场景:搜索框输入联想(停止输入 300ms 后再发请求)、表单实时校验、窗口尺寸调整结束后重新计算布局。
如果需要"首次立即执行、后续等待"的效果,可以增加 `immediate` 参数,在定时器建立前先判断并执行一次,这样用户点击按钮时能立刻得到反馈。
## 三、节流:固定频率执行
节流的思路是:不管事件触发多密集,都保证在单位时间内最多执行一次。常见实现有两种——时间戳版和定时器版。
```javascript
function throttle(fn, interval = 200) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn.apply(this, args);
}
};
}
```
时间戳版在第一次触发时立即执行,但最后一次触发可能被丢弃;定时器版能保证最后一次一定执行,但首次会有延迟。实际项目中通常把两者结合,既能立即响应,也不丢最后一次调用。
使用场景:滚动加载更多(每 200ms 检查一次位置)、鼠标移动轨迹采集、按钮防连点、进度条刷新。
## 四、两者的关键差异
| 维度 | 防抖 | 节流 |
| ---- | ----------- | ----------- |
| 执行时机 | 事件停止后延迟执行 | 固定间隔内执行一次 |
| 触发次数 | 连续触发只执行 1 次 | 连续触发按频率执行多次 |
| 典型场景 | 搜索联想、表单校验 | 滚动加载、坐标采集 |
| 用户体验 | 有等待感,但结果最新 | 响应及时,结果可能滞后 |
一句话总结:**防抖关注"最终状态",节流关注"过程节奏"**。判断用哪个,看业务是想要"最后一次的结果",还是想要"过程中的持续反馈"。
## 五、实战中的三个注意点
1. **保留 this 和参数**:包装函数里要用 `fn.apply(this, args)`,否则在类方法或事件回调中会丢失上下文。
2. **记得清理定时器**:组件卸载时要把 `timer` 清掉并置空,避免内存泄漏和卸载后仍然执行回调。
3. **不要过度优化**:只有确认该事件确实高频、且处理逻辑确实有开销时才加防抖节流。无脑套用会让代码可读性下降,还可能掩盖真正的性能问题。
## 六、小结
防抖和节流实现都不超过十行,但选错方案会让交互体验明显变差。判断标准很清晰:需要"稳定后的最终结果"用防抖,需要"平滑的持续响应"用节流。理解这两者的差异,比记住实现代码更重要。
- 点赞
- 收藏
- 关注作者
评论(0)