摘要: 滚动加载、按钮提交、搜索框建议……这些场景离不开它俩。


正文

朋友们,你是否遇到过这些让人头疼的问题?

  • 一个按钮被用户疯狂点击,提交了N次重复表单。❌

  • 页面滚动事件疯狂触发,复杂计算导致页面卡顿。❌

  • 搜索框输入时,每敲一个字就发一次请求,服务器压力山大。❌

别担心!今天要讲的 「节流(Throttle)」 和 「防抖(Debounce)」 ,就是专门治这毛病的“神医”!💊

这不仅是日常开发中的性能优化利器,更是前端面试中高频得不能再高频的经典考题。

本文将带你从概念到手写实现,彻底弄懂它们!文末还准备了两函数的终极版代码,欢迎收藏备用。


📚 一、概念:它俩到底是啥?

想象一下场景:

1. 防抖 (Debounce) - “等你说完我再动”

例子:电梯门。电梯不会有人一进就关,而是会等一段时间,如果一直有人进,门就会一直保持开着,直到没人进了(等待时间到了),最后才关门。

核心:事件被触发后,在等待一段时间内没有再触发,回调才会执行。如果在这段等待时间内事件又被触发,则重新开始计时

2. 节流 (Throttle) - “我说了算,每秒只能一次”

例子:游戏里的技能冷却。你按快捷键放技能,即使你疯狂连按,技能也只会按固定的频率释放一次,直到冷却结束。

核心:保证在一定时间间隔内,回调函数只执行一次

为了帮你更好理解,我画了一张它们工作流程的区别图:

如图所示,在事件持续触发时:

  • 节流 会像心跳一样,每隔固定时间就有规律地响应一次。

  • 防抖 则会在事件停止触发后,再延迟一段时间才最终响应一次。


💻 二、手把手代码实现

光说不练假把式,我们来手写一个增强版的节流和防抖函数,它支持配置是否立即执行

1. 防抖函数 (Debounce)

  • /**
     * 防抖函数
     * @param {Function} func 要执行的函数
     * @param {number} wait 等待时间(毫秒)
     * @param {boolean} immediate 是否立即执行
     * @return {Function}
     */
    function debounce(func, wait, immediate = false) {
      let timeout = null;
    
      return function (...args) {
        const context = this;
    
        // 如果已有定时器,清除它,重新开始计时
        if (timeout) clearTimeout(timeout);
    
        if (immediate) {
          // 如果尚未到等待时间,callNow 为 false,不执行
          // 如果已经过了等待时间,callNow 为 true,可以执行
          const callNow = !timeout;
          // 设置新的定时器,wait 时间后重置 timeout
          timeout = setTimeout(() => {
            timeout = null;
          }, wait);
          // 如果可以立即执行,则调用函数
          if (callNow) func.apply(context, args);
        } else {
          // 标准防抖:延迟执行
          timeout = setTimeout(() => {
            func.apply(context, args);
          }, wait);
        }
      };
    }

2. 节流函数 (Throttle)

我们用 Date.now() 和 setTimeout 结合的方式,实现一个既能立即执行第一次,又能在结束后执行最后一次的增强版节流。

/**
 * 节流函数
 * @param {Function} func 要执行的函数
 * @param {number} wait 等待时间(毫秒)
 * @return {Function}
 */
function throttle(func, wait) {
  let previous = 0; // 记录上一次执行的时间戳
  let timeout = null;

  return function (...args) {
    const context = this;
    const now = Date.now();
    const remaining = wait - (now - previous); // 计算剩余时间

    // 如果剩余时间小于等于0,说明可以执行了
    if (remaining <= 0) {
      // 如果存在定时器,清除掉,因为我们要立即执行
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now; // 更新上一次执行时间
      func.apply(context, args);
    } else if (!timeout) {
      // 如果剩余时间还没到,且没有设置定时器,则设置定时器在剩余时间后执行
      timeout = setTimeout(() => {
        previous = Date.now(); // 更新上一次执行时间
        timeout = null;
        func.apply(context, args);
      }, remaining);
    }
  };
}


🚀 三、应用场景:你用对地方了吗?

防抖 (Debounce) 用在:

  • 搜索框输入联想:等用户停止输入500毫秒后再请求搜索建议,避免请求过于频繁。

  • 窗口 resize 事件:等待用户调整浏览器窗口结束后,再计算布局,避免频繁重排重绘。

  • 文本编辑器自动保存:等用户停止编辑几秒后进行保存。

节流 (Throttle) 用在:

  • 按钮频繁点击(如提交、抢购):确保1秒内只提交一次,防止重复请求。

  • 页面滚动加载更多(无限滚动):滚动时每隔500ms检查一次位置,而不是每滚动1px都检查。

  • 游戏中的按键事件:确保技能按冷却时间释放。

简单记:防抖关心「最后一步」,节流关心「固定节奏」。


📦 四、终极懒人包

如果你不想自己手写,常用的工具库已经帮你准备好了!

  • Lodash_.debounce(func, [wait=0], [options={}]) 和 _.throttle(func, [wait=0], [options={}]),功能非常强大。

  • Underscore.js: 同样提供了 _.debounce 和 _.throttle

在项目中直接引入使用,是效率最高的选择。


❤️ 五、总结与互动

总结一下:

  • 防抖:连续事件结束后只执行一次。

  • 节流:连续事件每隔一段时间执行一次。

搞懂它们,不仅能让你在面试中游刃有余,更能实实在在地提升你开发应用的性能和用户体验。

#互动话题#
你在实际项目中用过节流或防抖吗?用在了什么有趣的场景?或者遇到过什么坑?

在评论区分享你的经验吧!

(关注下方微信公众号获取更多原创作品完整代码)

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐