ChuChencheng / note

菜鸡零碎知识笔记
Creative Commons Zero v1.0 Universal
3 stars 0 forks source link

JavaScript 实现 debounce 和 throttle #8

Open ChuChencheng opened 4 years ago

ChuChencheng commented 4 years ago

防抖(debounce)

概念

当调用一个函数时,先等待一段时间再调用,如果在等待期间又调用了这个函数,则重置等待的时间,以控制函数调用频率。

应用

例如输入框搜索建议。当用户输入内容的时候,触发函数去请求后台给用户提供建议,如果用户一个单词都没输入完,每个字母都去发起请求,就很没必要。这时可加入防抖,例如防抖 300ms ,用户输入内容时,如果 300ms 内没有再输入,则发起一次请求。

实现

function debounce (f, wait) {
  let timer = null
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
      f.call(this, ...args)
    }, wait)
  }
}

节流(throttle)

概念

对于一个函数的频繁调用,在一定时间内只调用一次。

应用

例如监听 scroll 事件实现懒加载,如果用户疯狂拖动滚动条却不拖到底下,绑定的处理函数就会疯狂被调用,这时候我们可以降低函数被调用的频率,不管用户怎么拖动,在一定时间(例如 300ms)内,都只会调用一次处理函数。

实现

function throttle (f, wait) {
  let last = 0
  return function (...args) {
    const now = Date.now()
    if (now - last > wait) {
      f.call(this, ...args)
      last = now
    }
  }
}

使用 setTimeout

function throttle (f, wait) {
  let timer = null
  return function (...args) {
    if (timer === null) {
      timer = setTimeout(() => {
        f.call(this, ...args)
        timer = null
      }, wait)
    }
  }
}

上述两种写法不同的是,setTimeout 是在 wait 时间的末尾才执行的第一次调用,且参数是第一次调用的参数;而第一种写法一开始就会调用一次。

如果 setTimeout 写法要执行最新一次的调用,即参数是最新的而不是第一次的,那需要在每次调用的时候更新 args ,否则 f.call(this, ...args) 中的 args 一直不会被更新:

function throttle (f, wait) {
  let timer = null
  let latestArgs = []
  return function (...args) {
    latestArgs = args
    if (timer === null) {
      timer = setTimeout(() => {
        f.call(this, ...latestArgs)
        timer = null
      }, wait)
    }
  }
}

拓展

以上写法已经达到了限制函数调用频率的目的,如果需要跟 lodash 这类库函数一样完善,还需要加上几个配置来决定是要在 wait 的开头还是末尾调用,同时提供清除等待等方法,这边就不再赘述,可自行去查阅源码。