防抖与节流

发布于:2022-12-20 ⋅ 阅读:(429) ⋅ 点赞:(0)

        在平时开发时,会有很多场景频繁触发事件,mousemove,resize,scroll等等,但是我们并不想频繁的触发这些事件,此时,就用到了防抖和节流 

以下面事件为例:

#box{

      height: 150px;

      line-height: 150px;

      text-align: center;

      color: #fff;

      background-color: #ccc;

      font-size: 80px;

    }

  <div id="box"></div>
 let num = 1

  let box = document.getElementById('box')

  function count() {

    box.innerText = num++

  }

 box.onmousemove = count  

一、防抖 

函数防抖:短时间内多次出发同一事件,只执行最后一次,或者只执行最开始的一次,中间的不执行

包括两个版本:非立即执行版本、立即执行版本

① 非立即执行版本

        指出发时间后不会立即执行,而是在n秒后执行,如果在n秒内又触发了事件,则会重新计算函数执行时间

    // 触发-不动-n秒后-执行

    // 触发-一直动-等什么时候不再触发时-n秒后-执行 

 function debounce(func,wait){

     let timer

     return function(){

       let boxtext = this   // 闭包函数中this指向父作用域,父作用域中this指向window

       let args = arguments

       if(timer)clearTimeout(timer)

       timer = setTimeout(()=>{

         func.apply(this,args)

       },wait)

     }

   }

   box.onmousemove = debounce(count,1000)

   ② 立即执行版本

        触发事件后函数就会立即执行,然后n秒内不触发事件才能继续执行函数

    // 触发-执行1次-不动-不触发-不执行

    // 触发-执行1次-n秒内-一直动-不触发-n秒内不动-n秒后动-只执行一次

    // 触发-执行1次-n秒后-一直动-触发第一次-执行1次

    // 触发-执行1次-n秒后-动一次-触发第一次-执行1次

   function debounce(func,wait) {

     let timer

     return function () {

       let boxtext = this

       let args = arguments

       if (timer) clearTimeout(timer)

       let callNow = !timer

       timer = setTimeout(()=>{

         timer = null

       },wait)

       if(callNow) func.apply(boxtext,args)

     }

   }

   box.onmousemove = debounce(count,1000)

  ③ 防抖-合成版 

// func 目标函数;wait 延迟执行毫秒数;immediate true-立即执行/false-延迟执行

// func 目标函数;wait 延迟执行毫秒数;immediate true-立即执行/false-延迟执行

  function debounce(func,wait,immediate) {

    let timer

    return function () {

      let boxtext = this

      let args = arguments

      if(timer) clearTimeout(timer)

      if(immediate){

        let callNow = !timer

        timer = setTimeout(()=>{

          timer = null

        },wait)

        if(callNow) func.apply(boxtext,args)

      }else{

        timer = setTimeout(() => {

          func.apply(boxtext,args)

        }, wait);

      }

    }

  }

二、节流

    节流:指连续触发事件但是在n秒中只执行一次函数。即2n秒内执行2次

    节流会稀释函数的执行频率

    同样有两个版本,时间戳版本和定时器版本

    ①  时间戳版本

        在持续触发事件的过程中,函数会立即执行,并且每1s执行一次

     function throttle(func, wait) {

       let previous = 0;

       return function () {

         let now = Date.now();

         let boxtext = this;

         let args = arguments;

         if (now - previous > wait) {

           func.apply(boxtext, args);

           previous = now;

         }

       };

     }

     box.onmousemove = throttle(count,1000)

    ② 定时器版本

        在持续触发事件的过程中,函数不会立即执行,并且每1s执行一次,在停止出发时间后,函数还会再执行一次

     function throttle(func,wait) {

       let timeout

       return function () {

         let boxtext = this

         let args = arguments

         if(!timeout){

           timeout = setTimeout(() => {

             timeout = null

             func.apply(boxtext,args)

           }, wait);

         }

       }

     }

     box.onmousemove = throttle(count,1000)

③ 时间戳 和 定时器 两个版本的不同

        时间戳版本和定时器版本的节流函数的区别就是,时间戳版的函数执行是在时间段内开始的时候,而定时器版的函数执行是在时间段内结束的时候

 ④ 合成版本

    // type 1 表时间戳版,2 表定时器版   

function throttle(func, wait, type) {

      if (type === 1) {

        let previous = 0;

      } else if (type === 2) {

        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);

          }

        }

      };

    }

三、注意

关于节流/防抖中boxtext(this)的指向解析:

      在执行throttle(count,1000)这行代码时,会有一个返回值,这个返回值是一个新的匿名函数,因此box.onmousemove = throttle(count,1000)这句话最终可以这样理解:

 content.onmousemove = function() {

        let now = Date.now();

        let context = this;

        let args = arguments;

        ...

        console.log(this)

      }

      ⭐this的具体指向需要到真正运行时才能确定下来

      当触发onmousemove事件的时候,才真正执行了上述的匿名函数,此时上高数的匿名函数的执行是通过 对象.函数名() 来完成的,那么函数内部的this指向对象

      匿名函数内部的func的调用方式如果是最普通的直接执行func(),那么func内部的this必定指向window,虽然在代码简单的情况下看不出异常(结构表现和正常一样)但是这将会是一个隐藏bug,所以,通过匿名函数捕获this,然后通过func.apply()的方式,来达到 content.onmousemove = func 这样的效果

      可以说,高阶函数内部都要注意this的绑定

四、区别

相同点:

  • 都可以通过使用 setTimeout 实现
  • 目的都是,降低回调执行频率。节省计算资源

不同点:

  • 函数防抖,在一段连续操作结束后,处理回调,利用clearTimeout和 setTimeout实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能
  • 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次

例如,都设置时间频率为500ms,在2秒时间内,频繁触发函数,节流,每隔 500ms 就执行一次。防抖,则不管调动多少次方法,在2s后,只会执行一次

如下图所示:

 


网站公告

今日签到

点亮在社区的每一天
去签到