JavaScript防抖与节流

目录

防抖(Debounce)

一、防抖的定义

二、防抖的实现原理

三、防抖的代码实现

四、代码解析

五、使用示例

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

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

六、总结

节流(Throttle)

一、节流的定义

二、节流的实现原理

三、节流的代码实现

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

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

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

四、代码解析

五、使用示例

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

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

六、总结

对比:防抖 vs 节流

源码解析

一、_.throttle() 源码解析

1. 核心逻辑

2. 关键源码步骤

3. 核心特性

二、_.debounce() 源码解析

1. 核心逻辑

2. 关键源码步骤

3. 核心特性


防抖(Debounce)


一、防抖的定义

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

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

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


二、防抖的实现原理

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

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

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

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


三、防抖的代码实现

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

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. 确保原始函数 func 的 this 指向正确(指向触发事件的元素)。

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

  4. 两种模式的区别

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

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


五、使用示例

1. 输入框实时搜索(延迟执行模式)
<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. 按钮防重复点击(立即执行模式)
<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>

六、总结

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

  • 实现要点

    • 使用 setTimeout 和 clearTimeout 管理延迟。

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

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

  • 应用场景

    • 输入框实时搜索建议。

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

    • 防止按钮重复提交。


   

节流(Throttle)


一、节流的定义

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

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

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


二、节流的实现原理

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

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

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

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


三、节流的代码实现

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

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

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

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. 定时器方式(延迟执行)

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

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. 结合时间戳和定时器(首尾均执行)

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

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 方法的作用

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

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

  4. 不同实现方式的区别

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

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

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


五、使用示例

1. 滚动事件节流(时间戳方式)
<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. 按钮防重复点击(定时器方式)
<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 秒内又触发了事件,则会重新计算函数执行时间

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

2. 节流(throttle)

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

    //节流简单写法
    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. 关键源码步骤
_.throttle(func, [wait=0], [options={}])
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. 关键源码步骤
_.debounce(func, [wait=0], [options={}])
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 选项允许首次触发立即执行。

  • 灵活控制cancel 和 flush 方法提供外部控制能力。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值