📖 《时间守护者丹尼的防抖传奇》

98 阅读3分钟

第一章:混乱的王国

在数字王国里,有个急躁的体操王子👑,他每次练功都疯狂触发动作请求,导致王宫服务器濒临崩溃!国王召见了神秘的时间守护者——防抖侠丹尼,他带着三件神器降临:
1️⃣ 「冷静沙漏」(wait):强制每次动作后必须冷静等待
2️⃣ 「即刻之刃」(leading):可立即响应首个紧急请求
3️⃣ 「最终钟摆」(trailing):确保处理最后一个请求


第二章:恶龙大作战

🐉 恶龙一:时间倒流
巫师篡改系统时钟,导致计时混乱!丹尼掏出沙漏检测到timeSinceLastCall < 0,大喝:"时光逆流?立即执行! " 一剑斩断异常状态。

🦹 恶龙二:无限重置
敌人用高频攻击让沙漏永远重置。丹尼祭出终极武器:「maxWait盾牌」,怒吼:"任你疯狂攻击,每200毫秒必反杀一次! "

📜 恶龙三:假传圣旨
有人冒充国王传假指令(非函数参数)。丹尼慧眼识破,抛出TypeError卷轴:"非函数,斩立决! "


第三章:运作原理

image.png

🔑 三神器组合技

  • 「冷静沙漏+最终钟摆」 = 传统防抖
  • 「即刻之刃+最终钟摆」 = 首尾兼顾模式
  • 「maxWait盾牌」 = 防永久沉默保险

终章:王国的启示

当丹尼卸下战甲,人们发现铠甲由这些代码铸造:

/**
 * 防抖函数:控制函数执行频率,支持立即执行和最大等待时间
 * @param {Function} func - 需要防抖的目标函数
 * @param {number} wait - 等待时间(毫秒)
 * @param {Object} [options] - 配置选项
 * @param {boolean} [options.leading=false] - 是否立即执行首次调用
 * @param {boolean} [options.trailing=true] - 是否执行最后一次调用
 * @param {number} [options.maxWait] - 最大等待时间(保证最低执行频率)
 */
function debounce(func, wait, options = {}) {
  // 参数校验
  if (typeof func !== 'function') {
    throw new TypeError('Expected a function');
  }

  // 配置解析(使用默认值+解构语法)
  const {
    leading = false,
    trailing = true,
    maxWait: customMaxWait
  } = options;
  const hasMaxWait = customMaxWait !== undefined;
  const maxWait = hasMaxWait ? Math.max(customMaxWait, wait) : null;

  // 状态管理(封装为对象提升可读性)
  const state = {
    lastThis: null,      // 最后一次调用的this上下文
    lastArgs: null,      // 最后一次调用的参数
    lastCallTime: null,  // 最后一次触发防抖的时间戳
    lastInvokeTime: 0,   // 最后一次实际执行函数的时间戳
    timeoutId: null      // 当前定时器ID
  };

  // 核心方法:执行目标函数
  const invokeFunction = (currentTime) => {
    state.lastInvokeTime = currentTime;
    state.lastArgs = state.lastThis = null; // 执行后清空上下文
    return func.apply(state.lastThis, state.lastArgs);
  };

  // 判断是否应该执行
  const shouldInvoke = (currentTime) => {
    const timeSinceLastCall = currentTime - state.lastCallTime;
    const timeSinceLastInvoke = currentTime - state.lastInvokeTime;

    // 执行条件(满足其一即可):
    return (
      state.lastCallTime === null ||                     // 首次调用
      timeSinceLastCall >= wait ||                       // 超过等待时间
      timeSinceLastCall < 0 ||                          // 系统时间异常
      (hasMaxWait && timeSinceLastInvoke >= maxWait)    // 超过最大等待时间
    );
  };

  // 计算剩余等待时间
  const calcRemainingWait = (currentTime) => {
    if (hasMaxWait) {
      const timeSinceInvoke = currentTime - state.lastInvokeTime;
      const timeSinceCall = currentTime - state.lastCallTime;
      const remainingFromWait = wait - timeSinceCall;
      const remainingFromMax = maxWait - timeSinceInvoke;
      return Math.min(remainingFromWait, remainingFromMax);
    }
    return wait - (currentTime - state.lastCallTime);
  };

  // 启动定时器流程
  const startTimer = (pendingFunc, delay) => {
    clearTimeout(state.timeoutId); // 清除旧定时器
    state.timeoutId = setTimeout(pendingFunc, delay);
  };

  // 后缘调用处理(延迟执行)
  const trailingEdge = (currentTime) => {
    state.timeoutId = null;
    if (trailing && state.lastArgs) {
      return invokeFunction(currentTime);
    }
    state.lastArgs = state.lastThis = null; // 无执行则清空参数
  };

  // 定时器到期时的处理
  const timerExpired = () => {
    const currentTime = Date.now();
    if (shouldInvoke(currentTime)) {
      return trailingEdge(currentTime);
    }
    // 未满足条件则重启定时器
    startTimer(timerExpired, calcRemainingWait(currentTime));
  };

  // 前缘调用处理(立即执行)
  const leadingEdge = (currentTime) => {
    state.lastInvokeTime = currentTime;
    startTimer(timerExpired, wait); // 启动后缘定时器
    return leading ? invokeFunction(currentTime) : null;
  };

  // 主入口函数
  const debounced = function (...args) {
    const currentTime = Date.now();
    state.lastArgs = args;      // 保存最新参数
    state.lastThis = this;      // 保存调用上下文
    state.lastCallTime = currentTime;

    // 判断是否需要立即执行
    if (shouldInvoke(currentTime)) {
      if (state.timeoutId === null) { // 无活跃定时器时触发前缘
        return leadingEdge(currentTime);
      }
      // 处理最大等待时间逻辑
      if (hasMaxWait) {
        startTimer(timerExpired, wait);
        return invokeFunction(currentTime);
      }
    }

    // 无活跃定时器时启动新定时器
    if (state.timeoutId === null) {
      startTimer(timerExpired, wait);
    }
  };

  // 附加方法:取消延迟执行
  debounced.cancel = () => {
    clearTimeout(state.timeoutId);
    state.timeoutId = null;
    state.lastCallTime = state.lastInvokeTime = 0;
    state.lastArgs = state.lastThis = null;
  };

  // 附加方法:立即触发执行
  debounced.flush = () => {
    return state.timeoutId === null ? null : trailingEdge(Date.now());
  };

  return debounced;
}

🌟 后世传颂:"真正的战士,不在于频繁出招,而在于在正确的时间,给出致命一击!" ——《防抖侠丹尼法典》