JavaScript防抖与节流:拯救你的网页卡顿危机!

发布于:2025-08-11 ⋅ 阅读:(13) ⋅ 点赞:(0)


在这里插入图片描述

🌪️ 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%

结语:成为性能优化大师

防抖和节流就像前端开发中的"呼吸法":

  • 防抖是深呼气后的暂停(等待稳定)
  • 节流是均匀的腹式呼吸(保持节奏)

记住这个开发者口诀:

“高频事件要优化,防抖节流来帮忙;
输入搜索用防抖,滚动动画节流上;
参数调优看场景,组合使用更健康!”

现在,打开你的开发者工具,找到那些"气喘吁吁"的事件处理器,用合适的策略给它们装上"呼吸调节器"吧! 🚀


网站公告

今日签到

点亮在社区的每一天
去签到