Skip to content
On this page

不是吧,我的节流函数似乎不太对?

节流函数作为前端最基础的工具函数,大家肯定都倒写入流了,但是它是不是有什么坑在里面呢?这篇文章带大家缕一缕!

写一个节流函数

替各位大佬花五分钟写个节流函数,如下:

ts
/**
 * 节流函数
 * @param fn 原函数
 * @param delay 延迟间隔
 * @param immediate 是否立即执行
 * @returns 节流函数
 */
export const _throttle = (
  fn: Function,
  delay: number = 500,
  immediate: boolean = true,
): Function => {
  let flag = true
  return function (this: any, ...rest: any[]) {
    if (flag) {
      if (immediate) fn.apply(this, rest)
      setTimeout(() => {
        if (!immediate) fn.apply(this, rest)
        flag = true
      }, delay)
      flag = false
    }
  }
}
  • 节流函数返回一个方法,执行时使用计时器节流。
  • 添加了一个immediate标志位,如果为true则立即执行再计时,否则即使结束再执行。

这能有啥问题?

节流函数的使用场景

提问题之前,我们想回想下节流函数的使用场景。我们通常会在某些触发频率无法控制的浏览器事件下使用节流,避免短时间多次触发回调导致性能问题,也避免不同浏览器内核造成的频率不同产生的影响。常见事件比如:mousemove、resize、scroll等等。

和这事件又有啥关系呢?

这些场景下我们事件的回调都需要获取事件对象,并且使用事件对象上的某个属性操作dom

举个🌰,我们通过监听window的resize事件,来做某些图表大小的调整,这时假设我们设置了节流函数延时200ms,在这200ms的开始触发了第一次事件我们的节流函数启动了定时器,并且将现在的回调放到了计时器中,等待200ms后执行,其他这200ms内的所有回调触发都会被我们略过。如果这200ms内,窗口大小变化非常大,并且在最后没有超出这200ms,那最后我们触发的回调也只会是第一次的。这就造成了图表大小不对,样式错位等等等问题。用户操作结束事件可以在这第一个200ms内,也可以在其他区间内,这就是个不可控因素。

当然你可以说,正常谁会这么拉窗口大小,这是个伪命题。这只是个🌰,其他情况可以自己想象。可能延时区间够小,他的问题就越看不出来,但是总有些时候有些特别的场景,让人头疼。

怎么解决?

其实知道问题解决也非常简单,首先把节流立即执行这个操作就很有问题,因为这样总是忽略了区间最后的参数。然后既然我们需要的是最后一个事件的参数,那我们搞个闭包把参数存起来就得了,最后计时器计时结束,我们执行使用存储的参数就搞定了。

ts
/**
 * 节流函数
 * @param fn 原函数
 * @param delay 延迟间隔
 * @returns 节流函数
 */
export const _throttle = (
  fn: Function,
  delay: number = 500,
): Function => {
  let flag = true
  let temp: any[]
  return function (this: any, ...rest: any[]) {
    temp = rest
    if (flag) {
      setTimeout(() => {
        fn.apply(this, temp)
        flag = true
      }, delay)
      flag = false
    }
  }
}

上次更新于: