JavaScript中的防抖与节流
字数 1517 2025-11-03 08:33:37

JavaScript中的防抖与节流

描述
防抖(debounce)和节流(throttle)是两种用于控制函数执行频率的技术,常用于优化高频触发的事件(如滚动、输入、窗口调整等)。防抖的核心是"延迟执行",在事件被频繁触发时,只有等到触发停止一段时间后才会执行函数。节流的核心是"稀释执行",确保函数在一定时间间隔内最多执行一次。

解题过程

  1. 理解问题场景
    想象一个搜索框,用户在输入时会实时向服务器发送请求。如果不加控制,每输入一个字符就发送一次请求,会造成巨大的性能浪费。我们需要限制请求的频率。

  2. 防抖(Debounce)的实现思路

    • 核心思想:事件被触发后,等待一个设定的延迟时间(如500毫秒)。如果在这个延迟时间内,事件又被触发,则取消上一次的等待,并重新开始计时。直到某次触发后,在延迟时间内没有再被触发,函数才会最终执行。

    • 生活比喻:就像电梯的自动关门。电梯门开后,如果不断有人进入(频繁触发),电梯会一直重新计算关门等待时间。直到最后一个人进入后,一段时间内没人再进,门才会关上(函数执行)。

    • 实现步骤
      a. 创建一个高阶函数,它接收两个参数:需要被防抖化的原始函数 func 和延迟时间 wait
      b. 在高阶函数内部定义一个变量(例如 timeoutId)来存储定时器的标识。
      c. 返回一个新的函数(例如闭包)。
      d. 在新函数内部,每次调用时,先使用 clearTimeout(timeoutId) 清除之前存在的定时器。
      e. 然后设置一个新的定时器,延迟 wait 毫秒后执行原始函数 func
      f. 将新定时器的标识赋值给 timeoutId

    • 基础代码实现

      function debounce(func, wait) {
        let timeoutId; // 用于存储定时器ID
      
        // 返回防抖化后的新函数
        return function (...args) {
          // 清除上一次的定时器
          clearTimeout(timeoutId);
      
          // 设置新的定时器
          timeoutId = setTimeout(() => {
            func.apply(this, args); // 使用apply确保func的this上下文和参数正确
          }, wait);
        };
      }
      
    • 使用示例

      // 一个模拟的搜索函数
      function search(query) {
        console.log(`正在搜索: ${query}`);
      }
      
      // 对search函数进行防抖化,延迟500毫秒
      const debouncedSearch = debounce(search, 500);
      
      // 模拟快速输入
      debouncedSearch('a'); // 被取消
      debouncedSearch('ap'); // 被取消
      debouncedSearch('app'); // 被取消
      // 等待500毫秒后,最终只会打印一次:正在搜索: app
      
  3. 节流(Throttle)的实现思路

    • 核心思想:确保函数在一个固定的时间间隔内只执行一次。无论事件触发有多频繁,函数都会按照设定的间隔规律地执行。

    • 生活比喻:就像水龙头,你即使把阀门拧到最大(频繁触发),水流也是匀速流出的(函数规律执行)。

    • 实现步骤(使用时间戳)
      a. 创建一个高阶函数,接收 func 和间隔时间 wait
      b. 定义两个变量:lastExecTime(上次执行的时间戳)和 timeoutId(定时器ID,用于尾调用)。
      c. 返回一个新的函数。
      d. 在新函数内部,获取当前时间 currentTime
      e. 计算距离下次可执行的时间 remaining = wait - (currentTime - lastExecTime)
      f. 如果 remaining <= 0,说明已经过了间隔时间,可以立即执行函数,并更新 lastExecTimecurrentTime
      g. 如果没有定时器在等待,并且 remaining > 0,则设置一个定时器,在 remaining 时间后执行函数(确保最后一次触发也能被执行)。

    • 代码实现

      function throttle(func, wait) {
        let lastExecTime = 0; // 上次执行时间
        let timeoutId; // 定时器ID
      
        return function (...args) {
          const currentTime = Date.now();
          const remaining = wait - (currentTime - lastExecTime);
      
          // 如果已过间隔时间,或者系统时间被调整(currentTime < lastExecTime)
          if (remaining <= 0 || currentTime < lastExecTime) {
            // 清除可能存在的尾调用定时器
            clearTimeout(timeoutId);
            timeoutId = null;
            lastExecTime = currentTime;
            func.apply(this, args);
          } 
          // 如果不在间隔时间内,且还没有设置尾调用定时器
          else if (!timeoutId) {
            timeoutId = setTimeout(() => {
              lastExecTime = Date.now(); // 使用当前时间更新最后一次执行时间
              timeoutId = null;
              func.apply(this, args);
            }, remaining);
          }
        };
      }
      
    • 使用示例

      // 一个处理滚动事件的函数
      function handleScroll() {
        console.log(`窗口滚动了,当前位置: ${window.scrollY}`);
      }
      
      // 对handleScroll函数进行节流化,每200毫秒最多执行一次
      const throttledScrollHandler = throttle(handleScroll, 200);
      
      // 当用户快速滚动页面时,handleScroll函数会以最大每200毫秒一次的频率被调用,而不是每次滚动都触发。
      window.addEventListener('scroll', throttledScrollHandler);
      
  4. 防抖与节流的对比与选择

    • 防抖:适用于连续事件结束后只需要触发一次的场景。
      • 例子:搜索框输入联想、窗口resize事件(等待用户调整完窗口大小后再计算布局)。
    • 节流:适用于连续事件中需要保持一定执行频率的场景。
      • 例子:页面滚动加载(scroll)、鼠标移动(mousemove)、射击游戏的射速限制。

总结
防抖和节流是前端性能优化中非常实用的技巧。防抖通过"重置延迟"来保证最终只执行一次,适合处理"结果"。节流通过"固定间隔"来保证执行频率,适合处理"过程"。理解它们的核心思想并掌握其实现原理,对于处理高频事件至关重要。

JavaScript中的防抖与节流 描述 防抖(debounce)和节流(throttle)是两种用于控制函数执行频率的技术,常用于优化高频触发的事件(如滚动、输入、窗口调整等)。防抖的核心是"延迟执行",在事件被频繁触发时,只有等到触发停止一段时间后才会执行函数。节流的核心是"稀释执行",确保函数在一定时间间隔内最多执行一次。 解题过程 理解问题场景 想象一个搜索框,用户在输入时会实时向服务器发送请求。如果不加控制,每输入一个字符就发送一次请求,会造成巨大的性能浪费。我们需要限制请求的频率。 防抖(Debounce)的实现思路 核心思想 :事件被触发后,等待一个设定的延迟时间(如500毫秒)。如果在这个延迟时间内,事件又被触发,则取消上一次的等待,并重新开始计时。直到某次触发后,在延迟时间内没有再被触发,函数才会最终执行。 生活比喻 :就像电梯的自动关门。电梯门开后,如果不断有人进入(频繁触发),电梯会一直重新计算关门等待时间。直到最后一个人进入后,一段时间内没人再进,门才会关上(函数执行)。 实现步骤 : a. 创建一个高阶函数,它接收两个参数:需要被防抖化的原始函数 func 和延迟时间 wait 。 b. 在高阶函数内部定义一个变量(例如 timeoutId )来存储定时器的标识。 c. 返回一个新的函数(例如闭包)。 d. 在新函数内部,每次调用时,先使用 clearTimeout(timeoutId) 清除之前存在的定时器。 e. 然后设置一个新的定时器,延迟 wait 毫秒后执行原始函数 func 。 f. 将新定时器的标识赋值给 timeoutId 。 基础代码实现 : 使用示例 : 节流(Throttle)的实现思路 核心思想 :确保函数在一个固定的时间间隔内只执行一次。无论事件触发有多频繁,函数都会按照设定的间隔规律地执行。 生活比喻 :就像水龙头,你即使把阀门拧到最大(频繁触发),水流也是匀速流出的(函数规律执行)。 实现步骤(使用时间戳) : a. 创建一个高阶函数,接收 func 和间隔时间 wait 。 b. 定义两个变量: lastExecTime (上次执行的时间戳)和 timeoutId (定时器ID,用于尾调用)。 c. 返回一个新的函数。 d. 在新函数内部,获取当前时间 currentTime 。 e. 计算距离下次可执行的时间 remaining = wait - (currentTime - lastExecTime) 。 f. 如果 remaining <= 0 ,说明已经过了间隔时间,可以立即执行函数,并更新 lastExecTime 为 currentTime 。 g. 如果没有定时器在等待,并且 remaining > 0 ,则设置一个定时器,在 remaining 时间后执行函数(确保最后一次触发也能被执行)。 代码实现 : 使用示例 : 防抖与节流的对比与选择 防抖 :适用于 连续事件结束后 只需要触发一次的场景。 例子 :搜索框输入联想、窗口 resize 事件(等待用户调整完窗口大小后再计算布局)。 节流 :适用于 连续事件 中需要保持一定执行频率的场景。 例子 :页面滚动加载(scroll)、鼠标移动(mousemove)、射击游戏的射速限制。 总结 防抖和节流是前端性能优化中非常实用的技巧。防抖通过"重置延迟"来保证最终只执行一次,适合处理"结果"。节流通过"固定间隔"来保证执行频率,适合处理"过程"。理解它们的核心思想并掌握其实现原理,对于处理高频事件至关重要。