Skip to content

怎么实现防抖和节流? ​

防抖:事件停下来,再等一段时间,才执行一次。节流:一段时间里最多执行一次。搜索输入用防抖,滚动和缩放用节流。两个都要能取消,并保留调用时的 this 和参数。

原理 ​

防抖每次触发都清掉上一个定时器。只有最后一次触发之后再等满 wait 毫秒,回调才运行。

js
function debounce(fn, wait) {
  let timer = null
  function debounced(...args) {
    const context = this
    clearTimeout(timer)
    timer = setTimeout(() => {
      timer = null
      fn.apply(context, args)
    }, wait)
  }
  debounced.cancel = () => {
    clearTimeout(timer)
    timer = null
  }
  return debounced
}

节流记录上次执行的时间。间隔已经够了就立刻执行;还没够就预约一次尾部调用,保证最后一次变化不会丢。

js
function throttle(fn, wait) {
  let last = 0
  let timer = null
  function throttled(...args) {
    const context = this
    const now = Date.now()
    const remaining = wait - (now - last)
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      last = now
      fn.apply(context, args)
      return
    }
    if (!timer) {
      timer = setTimeout(() => {
        last = Date.now()
        timer = null
        fn.apply(context, args)
      }, remaining)
    }
  }
  throttled.cancel = () => {
    clearTimeout(timer)
    timer = null
    last = 0
  }
  return throttled
}

上面的节流是「首部加尾部」:第一次马上执行,停止触发前如果还有没执行的变化,再补一次。只要首部时,把 setTimeout 那支去掉;只要尾部时,第一次也不要立刻执行。

追问 ​

按钮点击适合防抖吗?

提交按钮更适合「进行中就忽略下一次」,也就是加锁,而不是等用户停止点击。防抖会让第一次点击也推迟。搜索框才需要等输入停下来。

和 requestAnimationFrame 怎么选?

跟帧走的视觉更新用 requestAnimationFrame,一帧最多一次。跟网络请求走的输入用定时器防抖,等待时间由产品决定,不由刷新率决定。

易错点 ​

  • 防抖函数在定义时创建一次。写在渲染函数里每次都重新 debounce(...),定时器永远攒不起来。
  • 组件卸载或页面离开时调用 cancel,否则回调还会改已经卸掉的状态。
  • 箭头函数包一层时,里面的 this 不是事件目标。要 this 就使用普通函数,或者显式传入元素。

相关 ​