跳转到内容

节流

基础版

ts
/**
 * 节流
 *
 * @param fn 要节流的函数
 * @param delay 两次调用之间的最小时间间隔,以毫秒为单位
 * @param start 第一次是否执行
 * @returns 节流后的函数
 */
export function throttle<T extends any[]>(
  fn: (...args: T) => any,
  delay: number,
  start = true,
) {
  let last: number

  /**
   * @param {...any} args - 传递给节流函数的参数。
   */
  return function (this: any, ...args: T) {
    let now = Date.now()
    if ((start && !last) || now - last > delay) {
      fn.apply(this, args)
      last = now
    }
  }
}

lodash 版节流

ts
type ThrottleOptions = {
  /**
   * 是否在节流开始时执行一次函数
   */
  leading?: boolean;
  /**
   * 是否在节流结束时执行一次函数
   */
  trailing?: boolean;
};

/**
 * 节流函数
 * @param func 要节流的函数
 * @param wait 节流时间间隔
 * @param options 节流选项
 */
export function throttle<T extends (...args: any[]) => any>(
  func: T,
  wait: number,
  options: ThrottleOptions = {}
): (...args: Parameters<T>) => ReturnType<T> | void {
  let lastExecTime: number | undefined;
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
  let storedArgs: Parameters<T> | undefined;
  let storedThis: any;

  // 处理默认选项并确保至少有一个触发选项
  let { leading = true, trailing = true } = options;
  if (!leading && !trailing) {
    trailing = true;
  }

  // 清理定时器并重置状态
  const clearTimer = () => {
    if (timeoutId) {
      clearTimeout(timeoutId);
      timeoutId = undefined;
    }
  };

  // 执行 trailing 调用
  const trailingExec = () => {
    lastExecTime = Date.now();
    clearTimer();
    if (trailing && storedArgs) {
      func.apply(storedThis, storedArgs);
      storedArgs = undefined;
      storedThis = undefined;
    }
  };

  return function (this: any, ...args: Parameters<T>): ReturnType<T> | void {
    const now = Date.now();

    // 计算剩余时间
    const remaining = lastExecTime === undefined
      ? 0
      : wait - (now - lastExecTime);

    // 保存当前调用的上下文和参数
    storedArgs = args;
    storedThis = this;

    if (remaining <= 0) {
      clearTimer();
      lastExecTime = now;
      if (leading) {
        return func.apply(storedThis, storedArgs);
      }
    } else if (!timeoutId && trailing) {
      timeoutId = setTimeout(trailingExec, remaining);
    }

    // 非 leading 调用或无立即执行时返回 undefined
    return undefined;
  };
}