防抖和节流:核心区别与手写实现

177 阅读4分钟

前言

在前端开发中,频繁触发的事件(如 resize、scroll、输入框输入)会导致性能损耗,防抖(Debounce)和节流(Throttle)是解决这类问题的两大核心方案。二者看似相似,实则适用场景和实现逻辑截然不同,本文带你彻底搞懂它们的区别,并手把手实现基础版本。

一、核心定义:一句话分清防抖和节流

防抖(Debounce) :触发事件后,延迟 n 秒再执行回调;若在这 n 秒内再次触发事件,则重新计时,最终仅执行一次。

节流(Throttle) :触发事件后,每隔 n 秒强制执行一次回调;在这 n 秒内多次触发,也只会执行一次,核心是「控制执行频率」。

二、防抖(Debounce):像电梯关门一样 “等待最后一人”

1. 通俗理解

防抖的逻辑就像电梯关门:电梯原本计划 3 秒后关门,但若这 3 秒内又有人进入,就会重新计时 3 秒;直到某一次 3 秒内无人进入,电梯才会真正关门。核心是「等待最后一次触发」。

2. 典型应用场景

  • 搜索框实时联想:用户连续输入时,避免每输一个字就发请求,等输入停止后再查询;
  • 按钮防重复点击:避免用户快速点击提交按钮,导致多次请求接口;
  • 窗口 resize/scroll 高频触发:如调整浏览器窗口大小时,等调整停止后再重新计算布局。

3. 基础版防抖函数实现

 /**
   * 防抖函数
   * @param {Function} fn - 需要防抖的回调函数
   * @param {number} delay - 延迟执行的时间(毫秒)
   * @returns {Function} 包装后的防抖函数
   */
  function debounce(fn, delay) {
    let timer = null; // 维护一个定时器

    // 返回闭包函数,保留 timer 变量的作用域
    return function (...args) {
      clearTimeout(timer); // 每次触发时,清除之前的定时器
      // 重新设置定时器,延迟执行回调
      timer = setTimeout(() => {
        fn.apply(this, args); // 绑定 this 和参数,保证函数上下文正确
      }, delay);
    };
  }

  // 用法示例
  function handleResize() {
    console.log('页面尺寸已稳定,执行布局计算:', window.innerWidth);
  }
  // 包装防抖函数,延迟500ms执行
  const debounceResize = debounce(handleResize, 500);
  // 绑定窗口 resize 事件
  window.addEventListener('resize', debounceResize);

三、节流(Throttle):像水龙头一样 “固定频率出水”

1. 通俗理解

节流的逻辑就像调节水龙头:无论你怎么用力按,水龙头都只会每隔 3 秒滴一滴水,不会因为按得频繁就出水更多。核心是「控制执行间隔」,而非等待最后一次触发。

举个更贴近开发的例子:原本页面滚动时每 10ms 触发一次回调,用节流设置 200ms 间隔后,无论滚动多快,都只会每 200ms 执行一次,大幅降低执行频率。

2. 典型应用场景

  • 滚动加载:页面滚动时,每隔一段时间判断是否触底,避免高频检测;
  • 鼠标移动 / 拖拽:如拖拽元素时,每隔固定时间更新位置,而非实时更新;
  • 高频点击按钮:限制按钮点击频率(如秒杀按钮,防止 1 秒内多次点击)。

3. 基础版节流函数实现(时间戳 / 开关法)

  /**
   * 节流函数(开关法)
   * @param {Function} fn - 需要节流的回调函数
   * @param {number} interval - 执行间隔(毫秒)
   * @returns {Function} 包装后的节流函数
   */
  function throttle(fn, interval) {
    let isExecutable = true; // 标记是否可执行回调

    return function (...args) {
      // 若处于“冷却期”,直接返回
      if (!isExecutable) return;

      // 执行回调,并进入冷却期
      isExecutable = false;
      fn.apply(this, args); // 绑定 this 和参数
      // 间隔时间后,恢复可执行状态
      setTimeout(() => {
        isExecutable = true;
      }, interval);
    };
  }

  // 用法示例
  function handleScroll() {
    console.log('滚动检测:', window.scrollY);
  }
  // 包装节流函数,每200ms执行一次
  const throttleScroll = throttle(handleScroll, 200);
  // 绑定页面滚动事件
  window.addEventListener('scroll', throttleScroll);

四、进阶补充(可选)

本文实现的是基础版本的防抖和节流,生产环境中还可优化:

  1. 防抖支持「立即执行」(第一次触发时直接执行,后续触发延迟);
  2. 节流支持「时间戳版」(对比开关法,执行时机更精准);
  3. 增加取消功能(如 debounce.cancel() 手动清除定时器);
  4. 兼容 this 指向和参数传递(本文已通过 apply 处理)。

总结

  • 防抖:「等最后一次」,适合需要 “操作结束后再执行” 的场景;
  • 节流:「控频率」,适合需要 “持续触发但限制执行次数” 的场景;
  • 二者都是通过闭包保留变量状态,核心是利用定时器控制函数执行时机。

掌握这两个函数的核心逻辑,能有效解决前端高频事件的性能问题,也是面试中的高频手写考点。