文章目录

🌪️ JavaScript防抖与节流:拯救你的网页卡顿危机!
引言:当"狂点族"遇上网页性能
想象一下这个场景:小美正在双十一抢购,疯狂点击"立即购买"按钮;老王在调整浏览器窗口大小时,页面元素不断重新计算;小李在搜索框边输入边触发搜索…如果没有防抖和节流,这些操作会让网页像背着10斤重的书包跑步一样喘不过气来!
电商网站搜索框场景:
- 用户输入"最新智能手机"时:
- 无优化:触发6次搜索请求(每个字符一次)
- 防抖:只在停止输入300ms后触发1次
- 节流:每200ms最多触发1次(可能触发3-4次)
没有它们会怎样?
场景 | 问题 | 后果 |
---|---|---|
连续点击提交按钮 | 多次提交表单 | 重复订单、服务器压力大 |
窗口频繁调整大小 | 持续触发resize事件 | 页面卡顿、CPU占用高 |
输入框实时搜索 | 每次输入都请求API | 请求风暴、性能浪费 |
一、防抖(debounce):给"多动症"事件设置冷静期
1.1 什么是防抖?
“等你说完我再行动” - 就像电梯关门按钮,无论按多少次,只会在最后一次操作后等待一段时间才执行。
1.2 实现代码(带详细注释)
模式一:延迟执行(经典防抖)
/**
* 经典防抖:最后一次触发后等待delay毫秒执行
* @param {Function} fn - 目标函数
* @param {number} delay - 延迟时间(ms)
* @returns {Function} - 防抖处理后的函数
*/
function debounce(fn, delay) {
// 用于保存定时器ID的闭包变量
let timer;
// 返回一个包装后的新函数
return function(...args) {
// 如果已存在定时器,先清除之前的(避免多次触发)
if (timer) clearTimeout(timer);
// 设置新的定时器,在延迟时间后执行原函数
timer = setTimeout(() => {
// 使用apply保持原函数的this指向和参数
fn.apply(this, args);
// 执行后重置定时器变量(非必须,但有助于垃圾回收)
timer = null;
}, delay);
};
}
模式二:立即执行+冷却
/**
* 立即执行版防抖:首次触发立即执行,之后进入冷却期
* @param {Function} fn - 需要防抖的原始函数
* @param {number} wait - 冷却时间(毫秒)
* @returns {Function} - 经过防抖处理的新函数
*/
function debounceImmediate(fn, wait) {
// 用于保存定时器 ID 的闭包变量
let timer;
// 返回一个包装后的新函数
return function (...args) {
// 如果已经有定时器,先清除(重置冷却期)
if (timer) clearTimeout(timer);
// 判断是否应该立即执行
// 当 timer 为 null/undefined 时(即首次触发或已冷却完毕),shouldCallNow 为 true
const shouldCallNow = !timer;
// 设置新的定时器
// 定时器到期后把 timer 置空,表示冷却期结束
timer = setTimeout(() => {
timer = null;
}, wait);
// 如果需要立即执行,则立即调用原函数
if (shouldCallNow) {
fn.apply(this, args);
}
};
}
两种模式对比表
特性 | 延迟执行模式 | 立即执行模式 |
---|---|---|
首次触发 | 不执行 | 立即执行 |
后续触发 | 重置计时,最后一次执行 | 冷却期内不执行 |
执行时机 | 停止触发后delay毫秒 | 首次触发立即执行 |
适用场景 | 搜索建议、窗口resize | 按钮防重复点击、表单提交 |
1.3 适用场景(表格对比)
场景 | 不使用防抖 | 使用防抖后 |
---|---|---|
搜索框输入 | 输入每个字符都触发搜索 | 停止输入500ms后才搜索 |
窗口resize | 每次像素变化都计算布局 | 调整结束300ms后计算 |
按钮提交 | 快速点击导致多次提交 | 只认最后一次点击 |
二、节流(throttle):给事件加上"技能冷却时间"
2.1 什么是节流?
“再着急也得按节奏来” - 就像游戏中的技能CD,无论你按多快,技能都会按照固定频率释放。
2.2 实现代码
方式1:定时器版(尾部执行)
/**
* 定时器版节流(保证周期末尾执行)
* @param {Function} fn - 需要节流的函数
* @param {number} wait - 执行间隔(ms)
* @returns {Function} - 节流处理后的函数
*/
function throttleByTimer(fn, wait) {
let timer = null;
return function(...args) {
const context = this;
if (!timer) {
timer = setTimeout(() => {
fn.apply(context, args);
timer = null;
}, wait);
}
};
}
特点:
- 第一次触发后会等待
wait
毫秒才执行 - 保证每个周期末尾会执行一次
- 停止触发后可能还会执行最后一次
执行流程:
事件触发: |--x--x---x----x------x---|
执行时机: |----x----x----x----x-----|
(固定间隔末尾执行)
方式2:时间戳版(头部执行)
/**
* 时间戳版节流(保证周期开始执行)
* @param {Function} fn - 需要节流的函数
* @param {number} wait - 执行间隔(ms)
* @returns {Function} - 节流处理后的函数
*/
function throttleByTimestamp(fn, wait) {
let prev = 0; // 上次执行时间戳
return function(...args) {
const now = Date.now();
if (now - prev >= wait) {
fn.apply(this, args);
prev = now;
}
};
}
特点:
- 第一次触发立即执行
- 保证每个周期开始时执行
- 停止触发后不会再有额外执行
执行流程:
事件触发: |--x--x---x----x------x---|
执行时机: |x----x----x----x---------|
(固定间隔开始执行)
两种方式对比表格
特性 | 定时器版 | 时间戳版 |
---|---|---|
首次触发 | 延迟执行 | 立即执行 |
执行时机 | 周期末尾 | 周期开始 |
停止触发 | 可能执行最后一次 | 不会执行最后一次 |
实现方式 | 使用setTimeout | 使用Date.now() |
内存占用 | 需要维护timer变量 | 只需维护prev时间戳 |
适用场景 | 需要平滑结束的操作 | 需要快速响应的操作 |
2.3 防抖与节流对比(箭头图示)
高频事件触发场景:
快速连续触发事件
↓
防抖:└──────等待期──────┤执行最后一次
↑ 期间触发会重置等待期
节流:├─执行─┼──间隔──┼─执行─┤
固定时间间隔必执行
2.4 适用场景(表格对比)
场景 | 推荐技术 | 原因 |
---|---|---|
搜索建议 | 防抖 | 用户输入完毕才响应 |
无限滚动 | 节流 | 定期检查滚动位置 |
拖拽元素 | 节流 | 保持流畅的UI更新 |
窗口resize | 防抖/节流 | 防抖(最终布局)/节流(流畅调整) |
三、防抖与节流的全方位对比
3.1 相同之处
共同点 | 说明 |
---|---|
控制执行频率 | 两者都能减少高频事件的触发次数 |
提升性能 | 有效降低CPU使用率、减少不必要的函数调用 |
闭包应用 | 都利用闭包保存状态(timer/lastTime) |
返回新函数 | 都是高阶函数,返回包装后的函数 |
3.2 核心区别
特性 | 防抖(debounce) | 节流(throttle) |
---|---|---|
执行时机 | 最后一次触发后延迟执行 | 固定间隔执行 |
重置机制 | 新触发会重置计时 | 新触发不影响既定节奏 |
保证执行 | 快速连续触发时可能永不执行 | 必定会按节奏执行 |
内存占用 | 需要维护单个timer | 需要维护timer+lastTime |
适用场景 | 关注最终状态(如搜索) | 关注过程状态(如滚动) |
四、性能优化数据对比
通过Chrome DevTools测试相同场景:
操作 | 原生事件 | 防抖处理 | 节流处理 |
---|---|---|---|
输入100个字符 | 100次调用 | 1次调用 | 20次调用(每5字符) |
持续滚动10秒 | 500+次调用 | 1次调用 | 10次调用(每秒1次) |
CPU占用峰值 | 85% | 15% | 30% |
结语:成为性能优化大师
防抖和节流就像前端开发中的"呼吸法":
- 防抖是深呼气后的暂停(等待稳定)
- 节流是均匀的腹式呼吸(保持节奏)
记住这个开发者口诀:
“高频事件要优化,防抖节流来帮忙;
输入搜索用防抖,滚动动画节流上;
参数调优看场景,组合使用更健康!”
现在,打开你的开发者工具,找到那些"气喘吁吁"的事件处理器,用合适的策略给它们装上"呼吸调节器"吧! 🚀