Skip to content

2676. 节流

2676. 节流

ts
type F = (...args: number[]) => void
function throttle(fn: F, t: number): F {
  let lastArgs: number[] = []
  let nextRunTime = 0
  let timer
  return function (...args) {
    const now = new Date().getTime()
    if (now >= nextRunTime) {
      nextRunTime = now + t
      fn(...args)
    } else {
      clearTimeout(timer)
      lastArgs = [...args]
      timer = setTimeout(() => {
        nextRunTime = Date.now() + t
        fn(...lastArgs!)
        lastArgs = []
        timer = null
      }, nextRunTime - now)
    }
  }
}

/**
 * const throttled = throttle(console.log, 100);
 * throttled("log"); // logged immediately.
 * throttled("log"); // logged at t=100ms.
 */

节流 vs 防抖

这两个概念经常会混在一起,但语义并不相同:

  • 防抖(debounce):连续触发时,只执行最后一次。适合搜索框联想。
  • 节流(throttle):连续触发时,保证固定间隔至少执行一次。适合滚动事件、resize。

节流的关键在于:高频调用下,执行机会不会一直被推迟到最后。

实现要点

这个实现主要处理了两件事:

首调用立即执行。 第一次调用时 now >= nextRunTimenextRunTime 初始为 0),直接执行并设置下一次允许执行的时间。

冷却期内保存最后一次调用。 如果在冷却期内有调用,用 setTimeout 在冷却结束时执行最后一次保存的 args。如果冷却期内来了多次调用,后面的覆盖前面的——clearTimeout 清除旧定时器,lastArgs 更新为最新的参数。这保证了冷却期结束后执行的是最新的调用,而不是最早排队的那个。

这里保留最后一次调用而不是第一次调用,主要是为了避免冷却结束后执行到过时参数。