节流和防抖
1. 为什么需要节流和防抖
浏览器中的许多事件触发频率非常高:
scroll事件:滚动时每秒触发数十次甚至上百次。resize事件:窗口大小改变时连续触发。input事件:用户在输入框中每敲击一个键都会触发。mousemove事件:鼠标移动时连续触发。
如果直接在这些事件的回调中执行复杂操作(如网络请求、DOM 操作、重渲染等),会导致:
- 性能下降、页面卡顿。
- 不必要的资源浪费(如发送大量重复请求)。
- 逻辑错乱(例如搜索框实时提示,每次按键都请求接口)。
节流 和 防抖 就是用来限制函数执行频率的两种策略。
2. 防抖(Debounce)
2.1 原理
防抖:在事件被触发后,延迟 n 秒再执行回调。如果在 n 秒内事件再次被触发,则重新计时。
简单理解:“你尽管触发,我只在最后一次触发后等待一段时间才执行”。
2.2 形象比喻
- 电梯关门:电梯门即将关闭时,有人进来了,门会重新打开并等待一段时间,直到没有人进入才真正关闭。
2.3 实现
基础版(立即执行? 延迟执行?)
javascript
function debounce(func, delay, immediate = false) {
let timerId;
return function(...args) {
const context = this;
if (timerId) clearTimeout(timerId);
if (immediate) {
// 立即执行一次,然后等待 delay 内不再触发才能再次执行
const callNow = !timerId;
timerId = setTimeout(() => {
timerId = null;
}, delay);
if (callNow) func.apply(context, args);
} else {
timerId = setTimeout(() => {
func.apply(context, args);
}, delay);
}
};
}常见简化版(延迟执行):
javascript
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}2.4 应用场景
- 搜索框实时搜索(用户停止输入 500ms 后才请求)
- 窗口大小调整(resize)重新计算布局(调整结束后再计算)
- 表单验证(用户停止输入后验证)
- 保存草稿(用户停止编辑后自动保存)
3. 节流(Throttle)
3.1 原理
节流:在事件持续触发时,保证在一定时间间隔内只执行一次回调。
简单理解:“不管你触发多快,我每隔 n 秒才执行一次”。
3.2 形象比喻
- 机关枪射速限制:每秒最多射出 10 发子弹,即使你扣住扳机不放,也不会超过这个频率。
- 游戏技能冷却:技能释放后需要等待几秒才能再次释放。
3.3 实现
节流有两种常见实现方式:定时器(延迟执行,冷却结束后执行)和 时间戳(立即执行,然后冷却)。
方式一:定时器(第一次延迟执行,最后一次会执行)
javascript
function throttle(func, delay) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
}
};
}方式二:时间戳(第一次立即执行,最后一次可能被忽略)
javascript
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}方式三:组合版(支持首尾都执行)
javascript
function throttle(func, delay) {
let lastTime = 0;
let timer = null;
return function(...args) {
const now = Date.now();
const remaining = delay - (now - lastTime);
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
func.apply(this, args);
lastTime = now;
} else if (!timer) {
timer = setTimeout(() => {
func.apply(this, args);
lastTime = Date.now();
timer = null;
}, remaining);
}
};
}3.4 应用场景
- 滚动加载(监听滚动到底部加载更多,限制频率)
- 页面滚动时的动画或高亮(每 100ms 计算一次)
- 按钮重复点击(防止短时间内多次提交)
- 游戏中的射击频率限制
- 浏览器
resize事件(需要持续响应但不想太密集)
4. 防抖 vs 节流 对比表
| 特性 | 防抖(Debounce) | 节流(Throttle) |
|---|---|---|
| 执行时机 | 连续触发停止后,等待一段时间执行 | 固定时间间隔内执行一次 |
| 频率控制 | 将多次触发合并为最后一次 | 稀释为每隔 n 秒执行一次 |
| 适合场景 | 更关心“最后的结果” | 更关心“过程的频率控制” |
| 典型例子 | 搜索输入、resize 后计算 | 游戏射击、滚动加载、拖拽位置上报 |
| 是否可能丢失执行 | 可能丢失中间的执行(只执行最后一次) | 不会丢失(在周期内执行一次,但可能会跳过一些触发) |