防抖与节流:
一、为什么需要防抖节流?
想象用户在搜索框中快速输入 "防抖节流",如果每次按键都立即发送请求,会出现什么情况?
网络请求次数会呈爆炸式增长(假设输入速度为每秒 5 次,10 秒就是 50 次),页面可能因频繁渲染而卡顿甚至崩溃,服务器也会因压力骤增触发限流或返回错误。
这就是高频事件的危害。前端开发中常见的高频事件包括用户输入(input、keypress)、页面滚动(scroll)、窗口缩放(resize)、鼠标移动(mousemove)等。
防抖与节流正是为解决这类问题而生的性能优化技术,能将高频事件的触发次数从 "每秒百次" 降低到 "每秒几次" 甚至 "一次",显著提升页面流畅度和系统稳定性。
二、防抖(Debounce):等待最后一次操作
核心思想 :
当事件被触发后,延迟一段时间执行处理函数。若在这段时间内事件再次被触发,则重新计时,最终只有最后一次触发会被执行。
类比场景 :
电梯关门时,有人不断按关门键,电梯不会立即关门,而是等待一段时间(如 3 秒),确认没人再按后才关门;类似地,游戏中玩家点击回城按钮后,若被攻击则重新计时,直到安全等待时间结束才真正回城。
实现原理:
- 定时器:每次事件触发时,清除之前的定时器并重新设置。
- 闭包:利用闭包保存定时器,确保多次触发时共享同一状态。
- 上下文与参数传递:通过 apply 或 call 绑定 this,并传递事件参数。
基础代码实现(JavaScript):
javascript
function debounce(func, wait) {
let timeout = null;
return function(...args) {
const context = this;
if (timeout) clearTimeout(timeout); // 清除上一次定时器
timeout = setTimeout(() => {
func.apply(context, args); // 延迟执行函数
}, wait);
};
}
// 使用示例:搜索框防抖
const input = document.createElement('input');
document.body.appendChild(input);
const handleSearch = debounce((query) => {
console.log('搜索:', query);
}, 300); // 300ms延迟
input.addEventListener('input', (e) => {
handleSearch(e.target.value);
});
高级特性:
- 立即执行:允许在事件触发时立即执行一次函数,然后在等待时间内忽略后续触发。
ini
function debounce(func, wait, immediate = false) {
let timeout = null;
return function(...args) {
const context = this;
if (timeout) clearTimeout(timeout);
if (immediate && !timeout) { // 立即执行一次
func.apply(context, args);
}
timeout = setTimeout(() => {
if (!immediate) { // 非立即执行时延迟执行
func.apply(context, args);
}
}, wait);
};
}
- 返回值处理:若需要获取函数执行结果,可通过闭包保存返回值。
ini
function debounce(func, wait) {
let timeout = null;
let result = null;
return function(...args) {
const context = this;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
result = func.apply(context, args);
}, wait);
return result; // 立即返回最后一次执行结果
};
}
- 取消防抖:提供 cancel 方法手动清除定时器。
ini
function debounce(func, wait) {
let timeout = null;
const debounced = function(...args) {
const context = this;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
};
debounced.cancel = () => { // 新增取消方法
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
return debounced;
}
三、节流(Throttle):控制执行频率
核心思想 :
规定一个时间间隔,在该时间内,无论事件触发多少次,处理函数只执行一次。
类比场景 :
水龙头出水时,无论怎么快速开关,水都是按固定流速流出;游戏中技能释放后进入冷却时间,期间无法再次释放。
实现方式:
- 时间戳版:记录上一次执行时间,当当前时间与上一次时间差超过间隔时执行函数。
ini
function throttle(func, interval) {
let lastTime = 0;
return function(...args) {
const context = this;
const now = Date.now();
if (now - lastTime >= interval) {
func.apply(context, args);
lastTime = now;
}
};
}
- 定时器版:使用定时器控制执行频率,避免时间戳版可能的延迟累积。
ini
function throttle(func, interval) {
let timer = null;
return function(...args) {
const context = this;
if (!timer) {
timer = setTimeout(() => {
func.apply(context, args);
timer = null;
}, interval);
}
};
}
- 混合版:结合时间戳和定时器,确保首尾两次操作都能执行。
ini
function throttle(func, interval) {
let lastTime = 0;
let timer = null;
return function(...args) {
const context = this;
const now = Date.now();
if (now - lastTime >= interval) { // 立即执行
func.apply(context, args);
lastTime = now;
} else if (!timer) { // 延迟执行
timer = setTimeout(() => {
func.apply(context, args);
timer = null;
}, interval);
}
};
}
四、防抖与节流的对比
特性 | 防抖 | 节流 |
---|---|---|
触发时机 | 最后一次触发后等待 wait 时间执行 | 按 interval 固定频率执行 |
事件处理次数 | 可能被合并(多次触发→一次执行) | 严格限制频率(一定时间内至少一次) |
适用场景 | 需等待用户操作结束的场景(如搜索) | 需控制连续操作频率的场景(如滚动) |
内存消耗 | 依赖定时器,可能存在延迟执行 | 时间戳版无定时器,更精准 |

五、实际应用场景
防抖的典型场景 :
搜索框实时搜索时,用户输入结束后 300ms 发起请求;表单验证时,用户停止输入后检查邮箱格式;窗口缩放时,窗口停止调整后重新计算布局。
节流的典型场景 :
滚动加载时,滚动时每隔 1 秒请求一次数据;鼠标移动追踪时,限制 mousemove 事件处理频率;Canvas 绘制时,控制画笔绘制频率避免卡顿。
结合使用场景 :
复杂搜索场景可先节流(每秒最多 3 次)再防抖(最后一次输入后 300ms 请求);地图拖拽时,节流控制位置更新频率,防抖处理最终定位。
六、性能优化与注意事项
-
内存泄漏:防抖函数若未正确清除定时器,可能导致内存泄漏。解决方案是提供 cancel 方法手动清除定时器,或在组件卸载时取消防抖。
-
框架兼容性:在 React/Vue 中,建议将防抖节流逻辑封装为自定义 Hook 或混入(Mixin);在 Angular 中,可使用 RxJS 的 debounceTime 和 throttleTime 操作符。
-
第三方库:直接使用 Lodash 的_.debounce 和_.throttle,支持更多配置项。
javascript
import { debounce, throttle } from 'lodash';
// 防抖示例:立即执行,不执行最后一次
const handleSearch = debounce((query) => {
console.log('搜索:', query);
}, 300, { leading: true, trailing: false });
// 节流示例:延迟执行,执行最后一次
const handleScroll = throttle(() => {
console.log('滚动事件处理');
}, 1000, { leading: false, trailing: true });
七、总结
防抖适用于 "等待用户操作结束" 的场景,如搜索、表单验证;节流适用于 "控制事件执行频率" 的场景,如滚动、鼠标移动;结合使用能应对复杂需求,如先节流后防抖。
实际开发中,优先选择成熟的第三方库(如 Lodash),减少重复造轮子的风险。通过合理运用防抖与节流,可将高频事件的触发次数从 "洪水" 变为 "细流",让页面更流畅,系统更稳定。这两项技术不仅是前端性能优化的核心,也是面试中高频考点,建议深入理解其原理与实现细节。