全栈之旅:javaScript 进阶 - 防抖和节流

61 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

防抖和节流

防抖和节流的意义

当我们在使用windows的resize,scorll,mousemove,mousehover等方法时,浏览器会默认极高的频率去执行这些方法,这一定程度损失了浏览器的性能,进而影响用户体验,因此我们要对这些方法进行节流处理。 当我们在进行表单提交等与服务器进行交互时,为了防止用户无心的重复触发,所以进行防抖处理。

区别

  • 防抖:触发事件后一定时间内无再次触发,则执行事件
  • 节流:按规定时间重复执行事件

一、函数防抖

含义:当持续触发事件时,一定时间段内没有触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时,‘函数防抖’的关键在于,在一个动作发生一定时间之后,才会执行特定的事件。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <style>  
      #content {
        width: 200px;
        height: 200px;
        line-height: 200px;
        background-color: #aaa;        
        text-align: center;
        color: #000;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <div id="content"></div>
    <script>
      /*
      连续onmousemove在最后一次触发changeNum函数,
      多余的处理函数的都会被clearTimeout掉
      */
        let num=1
        let oDiv= document.getElementById('content')
        let changeNum=function () {
            oDiv.innerHTML=num++
        }
        let deBounce = function (fn,delay){
          let timer=null
          return function (...args) {
            if(timer) clearTimeout(timer)
            timer = setTimeout(()=>{
              fn(...args)
            },delay)
          }
        }
        oDiv.onmousemove=deBounce(changeNum,500)
        // or
        let _deBounce = deBounce(changeNum,500)
        oDiv.onmousemove=function(){
          _deBounce()
         }
    </script>
  </body>
</html>

当然,防抖也可以用CSS做,可以参考这篇文章,它是结合了pointer-events、animation、:active来实现的,有兴趣可以了解下。

二、函数节流(throttle)

含义:当持续触发事件时,保证一定时间段内只调用一次事件处理函数

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
  <button>点击</button>
  </body>
  <script>
    /*
     * 连续点击只会1000执行一次btnClick函数
     */
    let obutton = document.getElementsByTagName('button')[0]
    //  如果用箭头函数,箭头函数没有arguments,也不能通过apply改变this指向
    function btnClick() {
      console.log('点击成功')
    }
    /*
      方法1: 定时器方式实现
      缺点:第一次触发事件不会立即执行fn,需要等delay间隔过后才会执行
     */
    let throttle = (fn, delay) => {
      let flag = false
      return function(...args) {
        if (flag) return
        flag = true
        setTimeout(() => {
          fn(...args)
          flag = false
        }, delay)
      }
    }
    /*
     方法2:时间戳方式实现
     缺点:最后一次触发回调与前一次的触发回调的时间差小于delay,则最后一次触发事件不会执行回调
     */
    let throttle = (fn, delay) => {
      let _start = Date.now()
      return function(...args) {
        let _now = Date.now(),
          that = this
        if (_now - _start > delay) {
          fn.apply(that, args)
          start = Date.now()
        }
      }
    }

    // 方法3:时间戳与定时器结合
    let throttle = (fn, delay) => {
      let _start = Date.now()
      return function(...args) {
        let _now = Date.now(),
          that = this,
          remainTime = delay - (_now - _start)
        if (remainTime <= 0) {
          fn.apply(that, args)
        } else {
          setTimeout(() => {
            fn.apply(that, args)
          }, remainTime)
        }
      }
    }
    /*
      方法4:requestAnimationFrame实现
      优点:由系统决定回调函数的执行机制,60Hz的刷新频率,每次刷新都会执行一次回调函数,不
      会引起丢帧和卡顿
      缺点:1.有兼容性问题2.时间间隔有系统决定
      */
    let throttle = (fn, delay) => {
      let flag
      return function(...args) {
        if (!flag) {
          requestAnimationFrame(function() {
            fn.apply(that, args)
            flag = false
          })
        }
        flag = true
      }
    }

    obutton.onclick = throttle(btnClick, 1000)
  </script>
</html>