Skip to content

函数节流

概念

节流是指在一定时间内,无论事件被触发多少次,事件处理函数最多只会被执行一次。也就是说,即使事件连续触发,函数的执行也会被限定在一个固定的最小时间间隔内,确保在固定的时间间隔内至少有一次执行,但不会更频繁。

理解:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

应用场景

  • 滚动事件监听:在用户滚动页面时,节流可以确保滚动事件处理函数以固定的频率(如每秒一次)执行,而非每次滚动都触发,避免过于频繁的计算和更新造成卡顿。
  • 鼠标连续快速点击:在用户连续快速点击按钮时,节流可以确保在短时间内仅响应一次点击,避免重复操作带来的副作用,如多次提交表单。

极简版

时间戳法

第一次立即执行

js
function throttle(fn, wait) {
  let prev = 0
  return function(...args) {
    let context = this
    if (Date.now() - prev > wait) {
      fn.apply(context, args)
      prev = Date.now()
    }
  }
}

定时器法

第一次不立即执行

js
function throttle(fn, wait) {
  let timer
  return function(...args) {
    let context = this
    if (timer) return
    timer = setTimeout(() => {
      fn.apply(context, args)
      timer = null
    }, wait)
  }
}

时间戳法 + 定时器法

保证第一次和最后一次都执行

js
function throttle(fn, wait) {
  let timer
  let startTime = Date.now()
  return function(...args) {
    let currentTime = Date.now()
    let remainingTime = wait - (currentTime - startTime)
    let context = this
    if (timer) clearTimeout(timer)
    if (remainingTime <= 0) {
      fn.apply(context, args)
      startTime = Date.now()
    } else {
      timer = setTimeout(fn, remainingTime)
    }
  }
}

完整版

支持立即执行、结束后执行、取消

js
/**
* @desc fn 回调函数
* @desc wait 等待事件
* @desc immediate 是否立即执行一次
* @desc trail 执行结束后是否再执行一次
*/
function throttle(fn, wait, immediate, trail) {
  let timer
  let startTime = 0
  let clearTimer = () => {
    clearTimeout(timer)
    timer = null
  }
  let fn = function(...args) {
    let context = this
    if (!prev && !immediate) {
      startTime = Date.now()
    }
    let remainingTime = wait - (Date.now() - startTime)
    if (timer) clearTimer()
    if (!timer) {
      if (remainingTime <= 0 || remainingTime > wait) {
        startTime = Date.now()
        fn.apply(context, args)
      }
      if (trail) {
        timer = setTimeout(() => {
          startTime = immediate ? 0 : Date.now()
          timer = null
          fn.apply(context, args)
        }, remainingTime)
      }
    }
  }
  fn.off = function() {
    clearTimer()
    startTime = 0
  }
  return fn
}