「函数防抖」带你一次搞懂实现思路!

581 阅读3分钟

昨天刷抖音时,刷到渡一袁进老师讲解关于防抖一步一步的实现过程,不懂防抖的来学学,懂防抖的也来看看自己的理解是否正确,下面就复盘一下老师的思路。

作为前端开发,如果不会函数防抖,都不敢出门跟人打招呼,老师说的哈哈哈

场景

举个例子,在一个瀑布流布局中,我们需要根据窗口尺寸的改变而动态适配布局,这时候我们可能会这样写:

// 适配窗口布局
function layout() {
    //...
}
window.onresize = layout

监听窗口的 onresize 事件,在窗口尺寸发生变化时,重新进行布局,这样是能够实现效果,但是发现随着窗口尺寸频繁的变化,页面重新布局时会有些卡顿,造成效率问题,这是因为 onresize 事件会监听窗口每时每刻的变化,实际上我们只想看到拖动完窗口最后一次的布局效果,这时就满足了防抖的使用条件。

使用条件

要用上函数防抖,需要满足3个条件:

  • 频繁的调用某个函数
  • 会造成效率问题
  • 需要的结果以最后一次为准

❗注意:如果不满足这3个条件,就无需使用函数防抖,如果防抖的等待时间过长,会降低用户操作的实时性和流畅性,影响用户的操作感受,从而降低用户体验,所以使用它是有代价的,需要进行适当的权衡和调整,保证用户体验和性能的平衡。

防抖就像是电梯关门,当一个人进来后,门不会立马关上,比如说电梯会等2秒,如果期间又有人进来了,就重新开始计时等待,直到2秒内没人来后才真正关上门。如果把关门想象成函数,进来了3个人,结果只调用一次,以最后一次为准。

实现

下面我们来实现一下防抖的效果:

let timer
window.onresize = () => {
  clearTimeout(timer)
  timer = setTimeout(() => {
    layout()
  }, 500)
}

现在就实现了所需要的防抖效果,在调用函数时都会清空上一个定时器,并设置500毫秒的等待时长。但是这样写并不通用,万一我其他地方想用到呢?我们将防抖逻辑提取出来:

function debounce(func, duration = 500) {
  let timer
  return function () {
    clearTimeout(timer)
    timer = setTimeout(() => {
      func()
    }, duration)
  }
}
window.onresize = debounce(layout, 1000)

这样就初步提取了防抖的整体逻辑,传入需要执行的函数与等待时长(默认值500毫秒),但是又会发现万一传入的函数有参数呢?目前的实现就无法接受参数,我们再来改造一下:

function debounce(func, duration = 500) {
  let timer
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      func.apply(this, args)
    }, duration)
  }
}
window.onresize = debounce(layout, 1000)

在返回函数中,利用剩余参数来获取传入的实参,同时将参数传入方法以及绑定 this,这时就能够取到 onresize 事件的默认参数 event

需要格外注意的是函数中的 this 指向,不能使用箭头函数作为返回函数,因为箭头函数本身没有 this ,会绑定上一个不是箭头函数作用域内的 this 从而指向 window 而不是调用者本身。

总结

通过适用的场景来了解防抖,一步一步实现防抖函数,当然这里只是初步实现了最简单的版本,用意在于让大家了解整个防抖实现的思路,完全不需要背下来,只要一步一步的推导,如果还需要背就证明没有完全理解。

最后,如果想看防抖函数的具体实现,可以参考 lodashdebounce 的实现。