上海下雨了,不来点函数防抖、节流压压惊?

318 阅读4分钟

今年5月份毕业出来找工作的时候听说的函数防抖与节流,拖到现在采取整整的整理了解,之前也看过几篇关于函数节流和防抖的文章,但是作者都没好好检查下代码就把报错的代码发上来,所以今天 特意花点时间自己去好好了解函数防抖、函数节流,也遇到一些小坑,分享给大家,也作为自己的笔记。

首先要搞明白为什么要用函数防抖 函数节流,因为在开发时,有些操作调用函数十分的频繁,比如 resize、scroll、mousemove

我这边用的是mousemove事件做的例子

 <span id="span">0</span>//html
 let span = document.getElementById('span')//js
  let num = 0
  function mouseover (...args){
    console.log('mouseoverz执行了')
    num++
    this.innerHTML = num 
  }


函数防抖 debounce

当你连续触发函数的时候,函数只会被触发一次。主要有以下两种

1.非立即执行版


    //非 立即执行版
    const debounce = (func,wait,...args)=>{
      let timeout;
      return function () {
        let context = this;
        if(timeout) clearTimeout(timeout);
        timeout = setTimeout(()=>{
          func.apply(context,args);
        }, wait)
      }
    }
span.onmouseover = debounce(mouseover,1000,'a','b','c')

每次经过数字都会产生一个新的计时器,清除旧的计时器,直到你在1秒内不执行这个函数,目标函数才会被调用

在写这个函数的时候也发现了箭头函数和普通函数的一些区别

      //setTimeout 回调函数为非箭头函数
         const debounce = (func,wait,...args)=>{
          let timeout 
          return function (e){
            const context = this
            if(timeout) clearTimeout(timeout);
            timeout = setTimeout(function(){//setTimeout 返回值是这个额计时器一个唯一标志符
                         对于延时函数内部的回调函数的this指向全局对象window
              console.log(this)//window 有点意外吧 setTimeout是window的方法所以this指向window
              console.log(context)//这个好理解,谁去调用就指向哪个
              func.apply(context,args)//apply能够将数组自动转化为参数列表
            }, wait)
          }
        }

     //setTimeout 回调函数为箭头函数
     const debounce = (func,wait,...args)=>{
        let timeout 
        return function (e){
          const context = this
          if(timeout) clearTimeout(timeout);
          timeout = setTimeout(()=>{//setTimeout 返回值是这个额计时器一个唯一标志符对于
                                  延时函数内部的回调函数的this指向全局对象window
            console.log(this)//由于这里的函数时箭头函数,箭头函数的this指向的是定义时的this,而不是执行的this,
                                    所以这里的this会和context保持一致
            console.log(context)
            func.apply(context,args)//apply能够将数组自动转化为参数列表
          }, wait)
        }
      }

span.onmouseover = debounce(mouseover,1000,'a','b','c')


2.立即执行版

    const debounce = (func,wait,...args)=>{
      let timeout;
      return function (){
        let context = this
        if(timeout) clearTimeout(timeout);
        if(!timeout) func.apply(context,args);
        timeout = setTimeout(()=>{
          timeout = null
        }, wait)
      }
    }

每次经过数字都会产生一个新的计时器,清除旧的计时器,如果timeout为null 那么就会立即执行目标函数 必须间隔1秒后划过数字,函数才能再次被触发

结合版

 // 结合版
    const debounce = (func,wait,blo,...args) => {
      let timeout;
      return function () {
        let context = this;
        if(timeout) clearTimeout(timeout);
        if(blo){//立刻执行版
          if(!timeout) func.apply(context,args)
          timeout = setTimeout(()=>{
            timeout = null
          }, wait)
        }else{//非 立刻执行版
          timeout = setTimeout(()=>{
            func.apply(context,args)
          }, wait)
        }
      }
    }
    span.onmouseover = debounce(mouseover,1000,false,'a','b','c')

2.函数节流 throttle

函数节流:你连续触发某个函数,你可以控制这个函数多久触发一次 ,而函数防抖实际上是你连续触发某个函数,最终只会触发一次,你需要停止触发,过一段时间再去触发,才能再次触发成功

第一种:时间戳版


    // 时间戳版  //连续触发时 第一次会立即执行,之后秒执行一次
    const throttle = (func,wait,...args) => {
      let previous = 0;
      return function () {
        let context = this;
        let now = Date.now()
        if(now - previous > wait){
          func.apply(context,args)
          previous = now
        }
      }
    }

这里应该很清楚,不需要解释


第二种:计时器版


// 计时器版  //连续触发时 第一次不会立即执行,一秒后秒执行一次 所以会出现你停止不滑动后,数字最后跳了一次
    const throttle = (func,wait,...args)=>{
      let timeout;
      return function () {
        let context = this;
        if(!timeout){
          timeout = setTimeout(()=>{
            timeout = null;
            func.apply(context,args)
          },wait)
        }
      }
    }

节流结合版

这是我一开始写的,其实是有问题的,你能发现吗

    // 节流的结合版
    const throttle = (func,wait,type,...args) => {
      console.log(type === 1)
      if(type === 1){//时间戳版
        let previous = 0//
      }else if(type === 2){//定时器版
        let timeout
      }
      console.log(timeout)
      return function () {//时间戳版
        let context = this
        if(type === 1){
          let now = Date.now();
          if(now - previous > wait){
            func.apply(context,args)
            previous = now
          }
        }

        if(type === 2){//定时器版
          if(!timeout){
            timeout = setTimeout(()=>{
              timeout = null;
              func.apply(context, args)
            }, wait)
          }
        }
      }
    }
    span.onmouseover = throttle(mouseover,1000,1,'a','b','c')

看了报错,才找到的原因

// 节流的结合版
    const throttle = (func,wait,type,...args) => {
      console.log(type === 1)
      if(type === 1){//时间戳版
        var previous = 0//这里不能用let 因为有块级作用域,return出去function不能访问,所以要用var
      }else if(type === 2){//定时器版
        var timeout
      }
      console.log(timeout)
      return function () {//时间戳版
        let context = this
        if(type === 1){
          let now = Date.now();
          if(now - previous > wait){
            func.apply(context,args)
            previous = now
          }
        }

        if(type === 2){//定时器版
          if(!timeout){
            timeout = setTimeout(()=>{
              timeout = null;
              func.apply(context, args)
            }, wait)
          }
        }
      }
    }
    span.onmouseover = throttle(mouseover,1000,1,'a','b','c')