JS/TS 小技巧之防抖函数

概念

防抖是一种优化技术,用于减少高频率事件(如输入框输入、滚动事件等)触发的执行次数。

它会延迟执行目标函数,直到事件停止触发的一段时间后才执行。

如果在延迟时间内再次触发事件,则重新计时。

基础版

/**
 * 防抖函数 (Debounce)
 * 
 * @param func - 目标函数,表示需要被防抖处理的函数
 * @param delay - 延迟时间(毫秒),表示事件触发后多长时间执行目标函数
 * @returns 返回一个防抖后的函数
 */
function debounce<T extends (...args: any[]) => void>(func: T, delay: number): (...args: Parameters<T>) => void {
  // 定时器,用于记录延迟的时间
  let timer: NodeJS.Timeout | null = null;

  // 返回一个新的函数,用于包裹目标函数
  return function (...args: Parameters<T>) {
    // 如果定时器已经存在,清除当前定时器(重置计时)
    if (timer) {
      clearTimeout(timer);
    }

    // 设置一个新的定时器,在延迟时间到达后执行目标函数
    timer = setTimeout(() => {
      // 调用传入的目标函数,传递参数
      func(...args);
    }, delay);
  };
}

export default debounce;

进阶版

此版本的防抖函数支持以下功能:

  1. 保持 this 指向调用者。
  2. 支持立即执行:在第一次调用时立即执行函数,而不是等待延迟。
/**
 * 防抖函数 (Debounce)
 * 
 * @param func - 目标函数,表示需要被防抖处理的函数
 * @param delay - 延迟时间(毫秒),表示事件触发后多长时间执行目标函数
 * @param immediate - 是否在第一次调用时立即执行
 * @returns 返回一个防抖后的函数
 */
function debounce<T extends (...args: any[]) => void>(
  func: T,
  delay: number,
  immediate: boolean = false
): (...args: Parameters<T>) => void {
  // 定时器,用于记录延迟的时间
  let timer: NodeJS.Timeout | null = null;

  // 返回一个新的函数,用于包裹目标函数
  return function (this: any, ...args: Parameters<T>) {
    // 如果设置了立即执行
    const callNow = immediate && !timer;

    // 如果定时器已经存在,清除当前定时器(重置计时)
    if (timer) {
      clearTimeout(timer);
    }

    // 设置一个新的定时器,在延迟时间到达后执行目标函数
    timer = setTimeout(() => {
      timer = null; // 清空定时器标记
      if (!immediate) {
        func.apply(this, args); // 延迟执行目标函数
      }
    }, delay);

    // 如果是立即执行,则立即调用目标函数
    if (callNow) {
      func.apply(this, args);
    }
  };
}

export default debounce;

完整版

此版本的防抖函数支持以下功能:

  1. 保持 this 指向调用者。
  2. 支持立即执行:在第一次调用时立即执行函数,而不是等待延迟。
  3. 支持取消功能:可以通过 cancel 方法取消防抖中的目标函数执行。
  4. 支持强制执行:通过 flush 方法立即执行目标函数。
  5. 支持最大等待时间:即使持续触发事件,目标函数也会在最大等待时间到达时执行。
/**
 * 防抖函数 (Debounce)
 * 
 * @param func - 目标函数,表示需要被防抖处理的函数
 * @param delay - 延迟时间(毫秒),表示事件触发后多长时间执行目标函数
 * @param options - 配置选项
 *   - immediate: 是否在第一次调用时立即执行,默认为 false
 *   - maxWait: 最大等待时间(毫秒),即使持续触发事件,也会在该时间后执行目标函数
 * @returns 返回一个防抖后的函数,并附带 `cancel` 和 `flush` 方法
 */
function debounce<T extends (...args: any[]) => any>(
  func: T,
  delay: number,
  options: { immediate?: boolean; maxWait?: number } = {}
): {
  (this: any, ...args: Parameters<T>): void;
  cancel: () => void;
  flush: () => void;
} {
  const { immediate = false, maxWait } = options;
  let timer: NodeJS.Timeout | null = null; // 定时器,用于延迟执行
  let lastCallTime: number | null = null; // 上一次调用的时间
  let lastInvokeTime: number | null = null; // 上一次实际执行目标函数的时间
  let result: any; // 存储目标函数的返回值

  // 防抖函数主体
  function debounced(this: any, ...args: Parameters<T>): void {
    const now = Date.now();
    const isInvoking = shouldInvoke(now);

    // 如果设置了立即执行并且条件满足
    if (isInvoking) {
      if (!lastInvokeTime || immediate) {
        // 立即执行目标函数
        result = invokeFunc.call(this, now, args);
      }
    }

    // 如果定时器存在,清除当前定时器(重置计时)
    if (timer) {
      clearTimeout(timer);
    }

    // 设置最大等待时间的逻辑
    if (!lastCallTime) {
      lastCallTime = now;
    }

    // 如果最大等待时间已到,强制执行目标函数
    if (maxWait && now - lastCallTime >= maxWait) {
      result = invokeFunc.call(this, now, args);
    }

    // 设置新的定时器
    timer = setTimeout(() => {
      timer = null;
      if (!immediate) {
        result = invokeFunc.call(this, now, args);
      }
    }, delay);
  }

  // 判断是否应该立即执行目标函数
  function shouldInvoke(time: number): boolean {
    if (!lastInvokeTime) return immediate;
    return maxWait ? time - lastInvokeTime >= maxWait : false;
  }

  // 实际调用目标函数
  function invokeFunc(this: any, time: number, args: Parameters<T>) {
    lastInvokeTime = time;
    lastCallTime = null;
    return func.apply(this, args);
  }

  // 取消防抖功能
  debounced.cancel = function () {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    lastCallTime = null;
    lastInvokeTime = null;
  };

  // 强制执行目标函数
  debounced.flush = function () {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    if (lastCallTime) {
      result = invokeFunc.call(this, Date.now(), []);
    }
    lastCallTime = null;
    lastInvokeTime = null;
    return result;
  };

  return debounced;
}

export default debounce;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值