Skip to content

throttle 节流函数

在持续触发场景(滚动、窗口尺寸变化、鼠标移动等)中,节流可将频繁触发限制为固定时间窗口内最多执行一次,避免过度计算或渲染。

安装与引入

按需导入(推荐)

ts
import { throttle } from '@cuixingjian/cui-utils'

子路径导入

ts
import { throttle } from '@cuixingjian/cui-utils/throttle'

效果演示

基础用法

快速点击按钮,每 1000ms 最多执行一次。

使用代码

基础节流:

ts
import { throttle } from '@cuixingjian/cui-utils'

const onScroll = () => {
  console.log('处理滚动逻辑')
}

// 每 200ms 最多执行一次
const onScrollThrottled = throttle(onScroll, 200)
window.addEventListener('scroll', onScrollThrottled)

立即执行模式:

ts
import { throttle } from '@cuixingjian/cui-utils'

const resize = () => {
  console.log('处理窗口尺寸变化')
}

const resizeThrottled = throttle(resize, 300, { immediate: true })
window.addEventListener('resize', resizeThrottled)

取消与立即触发:

ts
import { throttle } from '@cuixingjian/cui-utils'

const handler = throttle(() => {
  console.log('执行处理')
}, 300)

// 取消等待中的尾触发
handler.cancel()

// 立即执行最后一次的调用(如果存在)
handler.flush()

API 说明

函数签名

ts
function throttle<T extends (...args: any[]) => any>(
  fn: T,
  wait?: number,
  options?: ThrottleOptions
): ThrottledFn<T>

参数

参数名类型默认值说明
fn(...args: any[]) => any需要节流的函数
waitnumber200时间窗口(毫秒)
optionsThrottleOptions{}配置选项

ThrottleOptions

参数类型默认值说明
immediatebooleanfalse是否在首次触发时立即执行(leading)

返回值

返回一个节流函数 ThrottledFn<T>,具有以下方法:

方法类型说明
cancel() => void取消等待中的尾触发
flush() => void立即执行最后一次的调用(如果存在)

注意事项

基本规则

  • 在组件卸载前如仍有节流等待中的尾触发,建议调用 cancel() 进行清理
  • 节流函数会保留 this 上下文和参数

immediate 模式说明

  • immediate: true 表示领先(leading)触发,首次立即执行
  • 尾触发仍可能在窗口末尾执行一次
  • 适合需要即时响应的场景

典型场景

  • 滚动事件监听:限制滚动处理频率,提升性能
  • 窗口尺寸变化:控制 resize 事件处理频率
  • 鼠标移动追踪:降低 mousemove 事件处理频率
  • 按钮连续点击:防止短时间内重复提交

源码

展开查看
ts
// 源码来自 @cuixingjian/cui-utils/throttle
export interface ThrottleOptions {
  immediate?: boolean
}

export interface ThrottledFn<T extends (...args: any[]) => any> {
  (...args: Parameters<T>): void
  cancel: () => void
  flush: () => void
}

export function throttle<T extends (...args: any[]) => any>(
  fn: T,
  wait = 200,
  options: ThrottleOptions = {}
): ThrottledFn<T> {
  let timer: ReturnType<typeof setTimeout> | null = null
  let lastInvokeTime = 0
  let lastArgs: Parameters<T> | null = null
  let lastThis: any = null
  let trailingPending = false
  const { immediate = false } = options

  const invoke = () => {
    if (lastArgs) {
      fn.apply(lastThis, lastArgs)
      lastArgs = null
      lastThis = null
    }
  }

  const throttled = function (this: any, ...args: Parameters<T>) {
    const now = Date.now()

    lastArgs = args
    lastThis = this

    if (immediate && (lastInvokeTime === 0 || now - lastInvokeTime >= wait)) {
      fn.apply(this, args)
      lastInvokeTime = now
      trailingPending = false
      if (!timer) {
        timer = setTimeout(() => {
          if (trailingPending) {
            invoke()
            lastInvokeTime = Date.now()
          }
          timer = null
          trailingPending = false
        }, wait)
      }
      return
    }

    trailingPending = true

    if (!timer) {
      const elapsed = lastInvokeTime ? now - lastInvokeTime : 0
      const remaining = wait - elapsed
      const delay = remaining > 0 ? remaining : wait
      timer = setTimeout(() => {
        invoke()
        lastInvokeTime = Date.now()
        timer = null
        trailingPending = false
      }, delay)
    }
  } as ThrottledFn<T>

  throttled.cancel = () => {
    if (timer) {
      clearTimeout(timer)
      timer = null
    }
    trailingPending = false
    lastArgs = null
    lastThis = null
  }

  throttled.flush = () => {
    if (timer) {
      clearTimeout(timer)
      timer = null
    }
    if (trailingPending || lastArgs) {
      invoke()
      lastInvokeTime = Date.now()
      trailingPending = false
    }
  }

  return throttled
}