防抖与节流

117 阅读1分钟

1. 来源(为什么需要防抖与节流)

在一些高频率事件触发的场景下,我们不希望对应的事件处理函数多次执行

2. 场景

  • 滚动事件
  • 输入的模糊匹配
  • 轮播图切换
  • 点击操作

3. 前期储备

浏览器默认情况下会有自己的监听事件间隔(4-6ms),如果监测到多次事件的监听执行,那么就会造成不必要的资源浪费

4. 前置场景

界面上有一个按钮,我们可以连续多次点击

5. 防抖与节流(概念描述)

  • 防抖:对于这个高频的操作来说,我们只希望识别一次,可以认为是第一次或最后一次
  • 节流:对于高频操作,我们可以自己来设置频率, 让本来会执行很多次的事件触发,按着我们定义的频率减少触发的次数

6. 防抖(代码)

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title></title>
</head>
<body>
	<button id="btn">Add</button>
	<script type="text/javascript">
		var btn = document.getElementById('btn')

		function btnClick(...args) {
			console.log('打印了', this, args)
		}

		function debounce(handle, wait, immediate) {
			if(typeof handle === 'undefined') throw new Error('handle must be a function')
			if(typeof wait === 'undefined') {
				wait = 300
				immediate = false
			}
			if(typeof wait === 'boolean') {
				wait = 300
				immediate = wait
			}
			if(typeof immediate !== 'boolean') immediate = false

			let timer = null
			return function Proxy(...args) {
				clearTimeout(timer)
				let init = immediate && !timer
				timer = setTimeout(() => {
					timer = null
					!immediate ? handle.call(this, ...args) : null
				}, wait)
				init ? handle.call(this, ...args) : null
			}
		}

		// btn.onclick = btnClick
		btn.onclick = debounce(btnClick, 500, true)
	</script>
</body>
</html>

7. 节流(代码)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>节流函数实现</title>
  <style>
    body {
      height: 5000px;
    }
  </style>
</head>

<body>
  <script>
    // 节流:我们这里的节流指的就是在自定义的一段时间内让事件进行触发

    function myThrottle(handle, wait) {
      if (typeof handle !== 'function') throw new Error('handle must be an function')
      if (typeof wait === 'undefined') wait = 400

      let previous = 0  // 定义变量记录上一次执行时的时间 
      let timer = null  // 用它来管理定时器

      return function proxy(...args) {
        let now = new Date() // 定义变量记录当前次执行的时刻时间点
        let self = this
        let interval = wait - (now - previous)

        if (interval <= 0) {
          // 此时就说明是一个非高频次操作,可以执行 handle 
          clearTimeout(timer)
          timer = null
          handle.call(self, ...args)
          previous = new Date()
        } else if (!timer) {
          // 当我们发现当前系统中有一个定时器了,就意味着我们不需要再开启定时器
          // 此时就说明这次的操作发生在了我们定义的频次时间范围内,那就不应该执行 handle
          // 这个时候我们就可以自定义一个定时器,让 handle 在 interval 之后去执行 
          timer = setTimeout(() => {
            clearTimeout(timer) // 这个操作只是将系统中的定时器清除了,但是 timer 中的值还在
            timer = null
            handle.call(self, ...args)
            previous = new Date()
          }, interval)
        }
      }

    }

    // 定义滚动事件监听
    function scrollFn() {
      console.log('滚动了')
    }

    // window.onscroll = scrollFn
    window.onscroll = myThrottle(scrollFn, 600)
  </script>
</body>

</html>