防抖(debounce)和节流(throttle)

6/24/2020 Javascript

# 概念部分

# 防抖(debounce)

触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

  • 输入框的 搜索联想,用户在不断输入值时,用防抖来节约请求资源
  • window 触发 resize 的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
  • 如果输入框的搜索联想不添加防抖函数,用户没输入一个字符都去请求一次接口,然后重新赋值给联想输入的列表,如此反复就真的会出现抖动的情况,所以防抖的名称不是白叫的。感兴趣可以自行体验下~

# 节流(throttle)

就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。

  • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
  • 就好像水龙头滴水,每间隔一段时间就流下一滴

# 防抖和节流的区别:

假设一个场景:有一个按钮,用户在 3s 内,以 0.1s 每次的速度点击。点击按钮后会触发一个函数。函数分别用防抖和节流来处理,查看区别:

  1. 防抖:因为以 0.5S/次 的速度点击,如果防抖控制的是每次触发时间不得小于 0.5s。那按钮最后触发的函数次数只有 1 次。因为前面几次点击都被重置,然后重新计算了函数执行时间。

  2. 节流:节流控制的也是 0.5s 内只执行一次。那么 3s 后,函数会被执行 7 次,最后一次是因为函数没再次被销毁。

demo 的实现

先通过引入 underscore 来使用防抖和节流

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/underscore.js/1.10.2/underscore-min.min.js"></script>
  </head>
  <body>
    <button id="btn">点击我</button>
    <script>
      let btn = document.getElementById('btn')
      var debounceFn = _.debounce(function() {
        console.log('触发防抖', new Date())
      }, 500)
      var throttleFn = _.throttle(function() {
        console.log('触发节流', new Date())
      }, 500)
      btn.addEventListener('click', debounceFn)
      btn.addEventListener('click', throttleFn)

      let allTime = 0
      let clickTime = setInterval(function() {
        if (allTime > 3000) {
          clearInterval(clickTime)
        } else {
          btn.click()
          allTime += 100
        }
      }, 100)
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

运行结果:

  • 防抖函数分为非立即执行版和立即执行版。

    • 非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

    • 立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。

# 通过学习 underscore 自己实现防抖和节流

用到的知识点:

  1. 闭包

    1.1 防抖和节流都用到了闭包,在函数内在返回一个函数,防抖中有timeout。节流中有 previoustimeout。使用闭包可以确保他们多次点击触发函数的时候访问的是同一个 timeout

  2. apply

    2.1 apply 用于改变 this 的指向,为了兼容 es5,所以 setTimeOut 中并没有使用箭头函数,如果不用 apply 改变 this 指向,那 this 就会指向 setTimeOut 中

    2.2 函数中可能接收多个参数,参数个数是不固定的,这时候我们可以用 arguments 获取参数数组,而 apply 第二个参数刚好可以接收一个数组

  3. 箭头函数拓展

防抖实现
/**
 * @desc 函数防抖
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param immediate true 表立即执行,false 表非立即执行
 */
function debounce(func, wait, immediate) {
  let timeout

  return function() {
    let context = this
    let args = arguments

    if (timeout) clearTimeout(timeout)
    if (immediate) {
      var callNow = !timeout
      timeout = setTimeout(() => {
        timeout = null
      }, wait)
      if (callNow) func.apply(context, args)
    } else {
      timeout = setTimeout(function() {
        func.apply(context, args)
      }, wait)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
节流实现
/**
 * @desc 函数节流
 * @param func 函数
 * @param wait 延迟执行毫秒数
 * @param type 1 表时间戳版,2 表定时器版
 */
function throttle(func, wait, type) {
  let previous = 0
  let timeout
  return function() {
    let context = this
    let args = arguments
    if (type === 1) {
      let now = Date.now()

      if (now - previous > wait) {
        func.apply(context, args)
        previous = now
      }
    } else if (type === 2) {
      if (!timeout) {
        timeout = setTimeout(() => {
          timeout = null
          func.apply(context, args)
        }, wait)
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Last Updated: 5/9/2021, 10:48:13 PM