防抖

基础知识

  1. 什么是防抖 (Debounce)? 防抖是一种性能优化策略,用于限制函数在特定时间内的执行频率。它的核心思想是:将连续的、密集的函数调用合并为一次,只在最后一次调用发生后的指定时间段内没有新的调用时,才真正执行该函数。

  2. 生活中的类比 想象一下电梯关门的场景:

    • 当有人走进电梯,门准备关闭的倒计时(比如 5 秒)开始。
    • 如果在倒计时结束前,又有人按了开门按钮或走了进来,倒计时会重置,重新开始 5 秒倒计时。
    • 只有当倒计时完整地走完,中间没有任何人再进来,门才会最终关闭。 在这个例子中,“关门”这个动作就被“防抖”了。
  3. 应用场景 防抖非常适用于那些由用户连续操作触发,但我们只关心其最终结果的场景:

    • 搜索框输入建议: 用户在输入框中连续输入时,我们不希望每输入一个字符就发送一次 API 请求,而是希望在用户停止输入一小段时间后再发送。
    • 窗口大小调整 (resize 事件): 当用户拖动调整浏览器窗口大小时,resize 事件会高频触发。我们不希望在过程中不断重新计算布局,而是在用户停止拖动后,再执行一次最终的布局计算。
    • 按钮重复点击: 防止用户因网络延迟等原因,快速重复点击提交按钮,导致表单被提交多次。

核心思路

防抖的核心实现机制,是巧妙地利用闭包定时器 (setTimeout)

  1. 高阶函数: 我们需要创建一个高阶函数 debounce。这个函数接收两个参数:要进行防抖处理的原始函数 func,以及一个延迟时间 delay。它会返回一个新函数

  2. 闭包与定时器 ID: 在 debounce 函数内部,但在返回的新函数外部,我们需要一个变量(例如 timer)来存储 setTimeout 返回的定时器 ID。由于闭包的特性,这个 timer 变量会在多次调用返回的新函数之间保持存在和共享。

  3. 返回的新函数(“防抖函数”)的逻辑:

    • 当这个新函数被触发时,它要做的第一件事就是清除之前设置的任何定时器(clearTimeout(timer))。这就是“重置倒计时”的核心步骤。
    • 然后,它会设置一个新的定时器setTimeout)。
    • 这个新的定时器会在指定的 delay 毫秒后,真正执行原始函数 func
  4. this 指向和参数传递: 原始函数 func 在执行时,可能需要正确的 this 上下文和参数。因此,在我们的防抖函数内部,需要捕获调用时的 thisarguments,并在最终执行 func 时通过 applycall 传递给它。

代码实现

下面我们提供一个基础版和一个功能更全面的进阶版实现。

版本一:基础实现

/**
 * 防抖函数(基础版)
 * @param {Function} func - 需要防抖的函数
 * @param {number} delay - 延迟时间,单位毫秒
 * @returns {Function} - 返回一个新的防抖函数
 */
function debounce(func, delay) {
  let timer = null; // 利用闭包保存定时器 ID

  return function (...args) {
    const context = this; // 保存调用时的 this 上下文

    // 如果已有定时器,则清除它
    if (timer) {
      clearTimeout(timer);
    }

    // 设置新的定时器
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

版本二:进阶实现(支持立即执行和取消)

/**
 * 防抖函数(进阶版)
 * @param {Function} func - 需要防抖的函数
 * @param {number} delay - 延迟时间,单位毫秒
 * @param {boolean} [immediate=false] - 是否在第一次触发时立即执行
 * @returns {Function} - 返回一个新的防抖函数,并附带 cancel 方法
 */
function debounce(func, delay, immediate = false) {
  let timer = null;
  let result;

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

    // 清除之前的定时器,以便重新计时
    if (timer) {
      clearTimeout(timer);
    }

    if (immediate) {
      // 1. "立即执行" 逻辑
      const callNow = !timer; // 如果 timer 为 null,说明是第一次调用
      timer = setTimeout(() => {
        timer = null; // delay 时间后,清空 timer,允许下一次“立即执行”
      }, delay);

      if (callNow) {
        result = func.apply(context, args);
      }
    } else {
      // 2. "非立即执行" 逻辑
      timer = setTimeout(() => {
        result = func.apply(context, args);
      }, delay);
    }

    // 对于立即执行的情况,返回上一次的结果
    return result;
  };

  // 3. 添加 cancel 方法
  debounced.cancel = function () {
    clearTimeout(timer);
    timer = null;
  };

  return debounced;
}

代码解析

基础版解析

  1. timer 变量核心作用

    • let timer = null 是防抖的核心机制,通过闭包被 debounce 函数返回的匿名函数共享。
    • 该变量如同「闹钟」,记录下一次函数执行的计划,确保在指定延迟内只执行一次函数。
  2. clearTimeout 重置机制

    • 每次防抖函数被调用时,先执行 clearTimeout(timer) 取消上一个未执行的「闹钟」。
    • 此操作实现「重置」效果,保证只有最后一次调用的延迟结束后才会执行函数。
  3. 上下文与参数处理

    • const context = this 保存调用时的上下文(如 DOM 元素)。
    • ...args 收集所有传入参数(如事件对象 event),确保原始函数调用时参数一致。
  4. 函数执行与上下文绑定

    • func.apply(context, args)setTimeout 回调中执行原始函数。
    • 通过 apply 确保函数执行时的 this 上下文和参数与最后一次调用防抖函数时一致。

进阶版解析

  1. immediate 立即执行逻辑

    • immediate: true 时,函数在事件触发时立即执行而非延迟结束后执行。
    • const callNow = !timer 巧妙判断是否处于冷却期(timer为null 表示可立即执行)。
    • callNowtrue,立即执行 func,并设置定时器在延迟后重置 timer
    • 定时器此时仅用于开启「冷却期」,而非执行函数,确保冷却期内多次调用不会重复执行。
  2. result 变量返回值处理

    • 用于存储 func 的返回值,仅在 immediate 模式下有效(函数同步执行)。
    • immediate 模式下函数异步执行,直接返回 undefined,是防抖的固有特性。
  3. debounced.cancel 方法

    • 通过给返回的 debounced 函数添加 cancel 方法暴露取消能力。
    • 调用 debounced.cancel() 会清除定时器并重置状态,常用于组件卸载时取消待处理任务(如 API 请求)。
Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""