JavaScript防抖与节流

目录

防抖(Debounce)

一、防抖的定义

二、防抖的实现原理

三、防抖的代码实现

四、代码解析

五、使用示例

[1. 输入框实时搜索(延迟执行模式)](#1. 输入框实时搜索(延迟执行模式))

[2. 按钮防重复点击(立即执行模式)](#2. 按钮防重复点击(立即执行模式))

六、总结

节流(Throttle)

一、节流的定义

二、节流的实现原理

三、节流的代码实现

[1. 时间戳方式(立即执行)](#1. 时间戳方式(立即执行))

[2. 定时器方式(延迟执行)](#2. 定时器方式(延迟执行))

[3. 结合时间戳和定时器(首尾均执行)](#3. 结合时间戳和定时器(首尾均执行))

四、代码解析

五、使用示例

[1. 滚动事件节流(时间戳方式)](#1. 滚动事件节流(时间戳方式))

[2. 按钮防重复点击(定时器方式)](#2. 按钮防重复点击(定时器方式))

六、总结

[对比:防抖 vs 节流](#对比:防抖 vs 节流)

源码解析

[一、.throttle() 源码解析](#一、.throttle() 源码解析)

[1. 核心逻辑](#1. 核心逻辑)

[2. 关键源码步骤](#2. 关键源码步骤)

[3. 核心特性](#3. 核心特性)

[二、.debounce() 源码解析](#二、.debounce() 源码解析)

[1. 核心逻辑](#1. 核心逻辑)

[2. 关键源码步骤](#2. 关键源码步骤)

[3. 核心特性](#3. 核心特性)


防抖(Debounce)


一、防抖的定义

防抖 是一种 优化高频触发事件 的技术,其核心思想是:在事件被频繁触发时,只有最后一次操作会被执行,中间的触发会被忽略

  • 典型场景:输入框实时搜索、窗口大小调整、滚动事件等需要限制执行频率的场景。

  • 核心目标:减少不必要的计算或请求,提升性能和用户体验。


二、防抖的实现原理

防抖的底层实现依赖以下技术点:

  1. 定时器(setTimeoutclearTimeout:用于控制事件触发的延迟时间。

  2. 闭包(Closure):保存定时器状态,确保多次触发时能共享同一个定时器。

  3. 函数包装:将原始函数包装成防抖函数,返回一个新函数供事件调用。


三、防抖的代码实现

以下是一个支持 立即执行延迟执行 的通用防抖函数:

javascript 复制代码
function debounce(func, wait, immediate = false) {
  let timeout = null;

  // 返回包装后的防抖函数
  return function (...args) {
    const context = this;

    // 如果定时器存在,清除之前的定时器(取消未执行的延迟操作)
    if (timeout) clearTimeout(timeout);

    if (immediate) {
      // 立即执行模式:首次触发立即执行,后续在 wait 时间内触发则重新计时
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null; // 恢复可执行状态
      }, wait);
      if (callNow) func.apply(context, args);
    } else {
      // 延迟执行模式:最后一次触发后等待 wait 时间再执行
      timeout = setTimeout(() => {
        func.apply(context, args);
      }, wait);
    }
  };
}

四、代码解析

  1. 参数说明

    1. func:需要防抖的原始函数。

    2. wait:防抖等待时间(单位:毫秒)。

    3. immediate:是否立即执行(true 表示首次触发立即执行,后续触发需等待)。

  2. 闭包保存状态

    1. timeout 变量通过闭包保存定时器 ID,确保多次触发共享同一状态。
  3. apply 方法的作用

    1. 确保原始函数 functhis 指向正确(指向触发事件的元素)。

    2. 传递事件参数(如 event 对象)。

  4. 两种模式的区别

    1. 立即执行模式 :首次触发立即执行函数,之后在 wait 时间内再次触发会重新计时,直到停止触发超过 wait 时间后,才能再次立即执行。

    2. 延迟执行模式 :每次触发都会重置计时,只有最后一次触发后等待 wait 时间才会执行。


五、使用示例

1. 输入框实时搜索(延迟执行模式)
html 复制代码
<input type="text" id="searchInput" />

<script>
  const searchInput = document.getElementById('searchInput');

  // 原始搜索函数
  function search(query) {
    console.log('搜索关键词:', query);
  }

  // 防抖处理(延迟执行)
  const debouncedSearch = debounce(search, 500);

  // 绑定输入事件
  searchInput.addEventListener('input', function (e) {
    debouncedSearch(e.target.value);
  });
</script>
2. 按钮防重复点击(立即执行模式)
html 复制代码
<button id="submitBtn">提交</button>

<script>
  const submitBtn = document.getElementById('submitBtn');

  // 原始提交函数
  function submitForm() {
    console.log('表单已提交');
  }

  // 防抖处理(立即执行)
  const debouncedSubmit = debounce(submitForm, 1000, true);

  // 绑定点击事件
  submitBtn.addEventListener('click', debouncedSubmit);
</script>

六、总结

  • 防抖的核心逻辑:通过定时器和闭包控制高频事件的执行时机。

  • 实现要点

    • 使用 setTimeoutclearTimeout 管理延迟。

    • 通过闭包保存定时器状态。

    • 处理 this 指向和参数传递。

  • 应用场景

    • 输入框实时搜索建议。

    • 窗口大小调整后的布局计算。

    • 防止按钮重复提交。


节流(Throttle)


一、节流的定义

节流 是一种 限制高频触发事件执行频率 的技术,其核心思想是:在事件被频繁触发时,固定时间间隔内只执行一次操作,忽略中间的触发

  • 典型场景:滚动事件、鼠标移动事件(如拖拽)、窗口大小调整、按钮频繁点击等。

  • 核心目标:在保证功能正常的前提下,降低事件处理频率,优化性能。


二、节流的实现原理

节流的底层实现依赖以下技术点:

  1. 定时器(setTimeoutclearTimeout)或时间戳:用于控制事件触发的间隔时间。

  2. 闭包(Closure):保存计时器状态,确保多次触发时能共享同一状态。

  3. 函数包装:将原始函数包装成节流函数,返回一个新函数供事件调用。


三、节流的代码实现

以下是两种常见的节流实现方式:

1. 时间戳方式(立即执行)

首次触发立即执行,之后在固定间隔内忽略后续触发。

javascript 复制代码
function throttle(func, wait) {
  let previous = 0; // 上次执行时间戳

  return function (...args) {
    const now = Date.now();
    const context = this;

    if (now - previous > wait) {
      func.apply(context, args);
      previous = now; // 更新执行时间戳
    }
  };
}
2. 定时器方式(延迟执行)

首次触发后等待固定时间执行,之后在固定间隔内忽略后续触发。

javascript 复制代码
function throttle(func, wait) {
  let timeout = null;

  return function (...args) {
    const context = this;

    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null; // 重置定时器
        func.apply(context, args);
      }, wait);
    }
  };
}
3. 结合时间戳和定时器(首尾均执行)

首次触发立即执行,最后一次触发在间隔结束后再执行一次。

javascript 复制代码
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);

    if (remaining <= 0) {
      // 立即执行
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      func.apply(context, args);
      previous = now;
    } else if (!timeout) {
      // 设置最后一次执行的定时器
      timeout = setTimeout(() => {
        func.apply(context, args);
        timeout = null;
        previous = Date.now();
      }, remaining);
    }
  };
}

四、代码解析

  1. 参数说明

    • func:需要节流的原始函数。

    • wait:节流间隔时间(单位:毫秒)。

  2. 闭包保存状态

    • previous(时间戳方式)或 timeout(定时器方式)通过闭包保存状态,确保多次触发共享同一计时器。
  3. apply 方法的作用

    • 确保原始函数 functhis 指向正确(如事件触发的元素)。

    • 传递事件参数(如 event 对象)。

  4. 不同实现方式的区别

    • 时间戳方式:立即响应首次触发,适合需要即时反馈的场景(如按钮点击)。

    • 定时器方式:延迟响应首次触发,适合连续触发但不需要立即执行的场景(如滚动事件)。

    • 结合方式:兼顾首尾执行,适用于需要更平滑响应的场景(如动画)。


五、使用示例

1. 滚动事件节流(时间戳方式)
html 复制代码
<div id="scrollArea" style="height: 2000px;"></div>

<script>
  const scrollArea = document.getElementById('scrollArea');

  // 原始滚动处理函数
  function handleScroll() {
    console.log('滚动位置:', window.scrollY);
  }

  // 节流处理(时间戳方式)
  const throttledScroll = throttle(handleScroll, 200);

  // 绑定滚动事件
  window.addEventListener('scroll', throttledScroll);
</script>
2. 按钮防重复点击(定时器方式)
html 复制代码
<button id="clickBtn">点击</button>

<script>
  const clickBtn = document.getElementById('clickBtn');

  // 原始点击处理函数
  function handleClick() {
    console.log('按钮点击');
  }

  // 节流处理(定时器方式)
  const throttledClick = throttle(handleClick, 1000);

  // 绑定点击事件
  clickBtn.addEventListener('click', throttledClick);
</script>

六、总结

  • 节流的核心逻辑:通过时间戳或定时器控制高频事件的执行频率。

  • 实现要点

    • 使用 Date.now()setTimeout 管理间隔时间。

    • 通过闭包保存计时器或时间戳状态。

    • 处理 this 指向和参数传递。

  • 应用场景

    • 滚动事件触发加载更多内容。

    • 鼠标移动时更新元素位置(如拖拽)。

    • 防止按钮频繁点击导致的重复提交。


对比:防抖 vs 节流

1. 防抖(debounce)

  • 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间

    javascript 复制代码
    //防抖简单写法
    function debounce(func, t) {
      let timer = null
      return function () {
        if (timer) {
          clearTimeout(timer)
        }
        timer = setTimeout(() => func(), t)
      }
    }

2. 节流(throttle)

  • 所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数

    javascript 复制代码
    //节流简单写法
    function throttle(func, t) {
      let timer = null
      return function () {
        if (!timer) {
          timer = setTimeout(() => {
            func()
            timer = null
          }, t)
        }
      }
    }

3. 对比:

特性 防抖(Debounce) 节流(Throttle)
触发频率 最后一次触发后等待 wait 时间执行 固定时间间隔内最多执行一次
适用场景 输入框搜索、窗口大小调整 滚动事件、鼠标移动事件、频繁点击按钮
核心目标 确保高频触发时只执行一次 确保高频触发时按固定频率执行

源码解析


一、_.throttle() 源码解析

1. 核心逻辑

节流函数确保在 wait 时间间隔内最多执行一次 func,支持首次(leading)和末次(trailing)执行控制。

2. 关键源码步骤
javascript 复制代码
_.throttle(func, [wait=0], [options={}])
javascript 复制代码
function throttle(func, wait, options) {
  let leading = true;
  let trailing = true;

  // 参数处理
  if (typeof options === 'object') {
    leading = 'leading' in options ? !!options.leading : leading;
    trailing = 'trailing' in options ? !!options.trailing : trailing;
  }

  let lastArgs, lastThis, result;
  let timeout = null;
  let previous = 0;

  const throttled = function(...args) {
    const now = Date.now();
    // 首次调用且不执行 leading 时,设置 previous 为 now
    if (!previous && leading === false) previous = now;

    // 计算剩余时间
    const remaining = wait - (now - previous);

    // 需要执行(剩余时间 <=0 或系统时间被修改)
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      previous = now;
      result = func.apply(this, args);
    } else if (!timeout && trailing !== false) {
      // 设置尾调用的定时器
      timeout = setTimeout(() => {
        previous = leading === false ? 0 : Date.now();
        timeout = null;
        result = func.apply(lastThis, lastArgs);
      }, remaining);
    }
    return result;
  };

  // 提供取消方法
  throttled.cancel = function() {
    clearTimeout(timeout);
    previous = 0;
    timeout = lastArgs = lastThis = null;
  };

  return throttled;
}
3. 核心特性
  • 时间戳与定时器结合 :既保证首次触发立即响应(leading),又在停止触发后执行最后一次(trailing)。

  • 系统时间篡改兼容 :检测到 remaining > wait 时强制触发。

  • 取消机制:允许手动取消未执行的尾调用。


二、_.debounce() 源码解析

1. 核心逻辑

防抖函数在连续触发时,仅在最后一次触发后等待 wait 时间执行一次 func,支持立即执行模式(leading)。

2. 关键源码步骤
javascript 复制代码
_.debounce(func, [wait=0], [options={}])
javascript 复制代码
function debounce(func, wait, options) {
  let lastArgs, lastThis, result;
  let timerId = null;
  let lastCallTime = 0;
  let leading = false;
  let maxing = false;
  let maxWait;

  // 参数处理
  if (typeof options === 'object') {
    leading = !!options.leading;
    maxing = 'maxWait' in options;
    maxWait = maxing ? Math.max(options.maxWait || 0, wait) : maxWait;
  }

  const invokeFunc = (time) => {
    const args = lastArgs;
    const thisArg = lastThis;
    lastArgs = lastThis = undefined;
    result = func.apply(thisArg, args);
    return result;
  };

  const leadingEdge = (time) => {
    // 记录最后一次调用时间
    lastCallTime = time;
    // 设置定时器
    timerId = setTimeout(timerExpired, wait);
    // 立即执行模式
    return leading ? invokeFunc(time) : result;
  };

  const shouldInvoke = (time) => {
    // 判断是否需要执行(超过 wait 或 maxWait)
    const timeSinceLastCall = time - lastCallTime;
    return (lastCallTime === 0) || (timeSinceLastCall >= wait) || 
           (maxing && timeSinceLastCall >= maxWait);
  };

  const debounced = function(...args) {
    const time = Date.now();
    lastArgs = args;
    lastThis = this;

    // 判断是否应该执行
    const isInvoking = shouldInvoke(time);
    if (isInvoking) {
      // 清除已有定时器
      if (timerId === null) {
        return leadingEdge(time);
      }
      // 处理 maxWait 场景
      if (maxing) {
        timerId = setTimeout(timerExpired, wait);
        return invokeFunc(time);
      }
    }
    // 设置/重置定时器
    if (timerId === null) {
      timerId = setTimeout(timerExpired, wait);
    }
    return result;
  };

  // 提供取消和立即执行方法
  debounced.cancel = function() { /* ... */ };
  debounced.flush = function() { /* ... */ };

  return debounced;
}
3. 核心特性
  • maxWait 支持:确保在超时后强制执行,避免长期不触发导致的延迟。

  • 立即执行模式leading 选项允许首次触发立即执行。

  • 灵活控制cancelflush 方法提供外部控制能力。


相关推荐
user77574297353152 分钟前
Echarts-Vue3-多图表联动
前端
清弦居士3 分钟前
解锁 Ant Design MCP 组件查询新姿势:大模型组件查询新范式
前端·mcp
天天扭码7 分钟前
LeetCode 题解 | 1.两数之和(最优解)
前端·javascript·算法
冉冉同学10 分钟前
【HarmonyOS NEXT】解决微信浏览器无法唤起APP的问题
android·前端·harmonyos
广龙宇13 分钟前
【Web API系列】Web Shared Storage API之WorkletSharedStorage深度解析与实践指南
前端
逍遥德19 分钟前
前端工程化-包管理NPM-package.json 和 package-lock.json 详解
前端·npm·json
一只小风华~20 分钟前
Web前端 (CSS篇)
前端·css·html·html5
HelloRevit24 分钟前
npm install 版本过高引发错误,请添加 --legacy-peer-deps
前端·npm·node.js
工九度26 分钟前
2025前端社招最新面试题汇总- 场景题篇
前端·javascript
AronTing26 分钟前
状态模式:有限状态机在电商订单系统中的设计与实现
前端·设计模式·面试