防抖

你是否在日常开发中遇到一个问题,在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。

这些需求都可以通过函数防抖动来实现。尤其是第一个需求,如果在频繁的事件回调中做复杂计算,很有可能导致页面卡顿,不如将多次计算合并为一次计算,只在一个精确点做操作。

PS:防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的 情况会每隔一定时间(参数wait)调用函数。

我们先来看一个袖珍版的防抖理解一下防抖的实现:

  1. // func是用户传入需要防抖的函数
  2. // wait是等待时间
  3. const debounce = (func, wait = 50) => {
  4. // 缓存一个定时器id
  5. let timer = 0
  6. // 这里返回的函数是每次用户实际调用的防抖函数
  7. // 如果已经设定过定时器了就清空上一次的定时器
  8. // 开始一个新的定时器,延迟执行用户传入的方法
  9. return function(...args) {
  10. if (timer) clearTimeout(timer)
  11. timer = setTimeout(() => {
  12. func.apply(this, args)
  13. }, wait)
  14. }
  15. }
  16. // 不难看出如果用户调用该函数的间隔小于wait的情况下,上一次的时间还未到就被清除了,并不会执行函数

这是一个简单版的防抖,但是有缺陷,这个防抖只能在最后调用。一般的防抖会有immediate选项,表示是否立即调用。这两者的区别,举个栗子来说:

  • 例如在搜索引擎搜索问题的时候,我们当然是希望用户输入完最后一个字才调用查询接口,这个时候适用延迟执行的防抖函数,它总是在一连串(间隔小于wait的)函数触发之后调用。
  • 例如用户给interviewMap点star的时候,我们希望用户点第一下的时候就去调用接口,并且成功之后改变star按钮的样子,用户就可以立马得到反馈是否star成功了,这个情况适用立即执行的防抖函数,它总是在第一次调用,并且下一次调用必须与前一次调用的时间间隔大于wait才会触发。

下面我们来实现一个带有立即执行选项的防抖函数

  1. // 这个是用来获取当前时间戳的
  2. function now() {
  3. return +new Date()
  4. }
  5. /**
  6. * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
  7. *
  8. * @param {function} func 回调函数
  9. * @param {number} wait 表示时间窗口的间隔
  10. * @param {boolean} immediate 设置为ture时,是否立即调用函数
  11. * @return {function} 返回客户调用函数
  12. */
  13. function debounce (func, wait = 50, immediate = true) {
  14. let timer, context, args
  15. // 延迟执行函数
  16. const later = () => setTimeout(() => {
  17. // 延迟函数执行完毕,清空缓存的定时器序号
  18. timer = null
  19. // 延迟执行的情况下,函数会在延迟函数中执行
  20. // 使用到之前缓存的参数和上下文
  21. if (!immediate) {
  22. func.apply(context, args)
  23. context = args = null
  24. }
  25. }, wait)
  26. // 这里返回的函数是每次实际调用的函数
  27. return function(...params) {
  28. // 如果没有创建延迟执行函数(later),就创建一个
  29. if (!timer) {
  30. timer = later()
  31. // 如果是立即执行,调用函数
  32. // 否则缓存参数和调用上下文
  33. if (immediate) {
  34. func.apply(this, params)
  35. } else {
  36. context = this
  37. args = params
  38. }
  39. // 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
  40. // 这样做延迟函数会重新计时
  41. } else {
  42. clearTimeout(timer)
  43. timer = later()
  44. }
  45. }
  46. }

整体函数实现的不难,总结一下。

  • 对于按钮防点击来说的实现:如果函数是立即执行的,就立即调用,如果函数是延迟执行的,就缓存上下文和参数,放到延迟函数中去执行。一旦我开始一个定时器,只要我定时器还在,你每次点击我都重新计时。一旦你点累了,定时器时间到,定时器重置为 null,就可以再次点击了。
  • 对于延时执行函数来说的实现:清除定时器ID,如果是延迟调用就调用函数