防抖和节流函数

450 阅读3分钟

前提

  • 防抖和节流的出现是为了让一个函数不这么频繁的发生,有时候无意义的函数(比如:请求数据)发生的太频繁会对服务器造成巨大的压力

具体案例

//当按下键盘的时候会持续触发这个事件
//需求:我们并不需要每次按下就触发,用户停止按下之后再触发这个事件
//具体化需求:可以再用户停止按键盘2s之后再触发,如果.5s内还按键盘的话不触发事件
<input id="ipt1" />
ipt1.onkeydown = function(){
  console.log(e.target.value);
}

简易版防抖

  • 原理:利用定时器来实现,小提示:定时器的变量是以一个自增的数字显示
      let ipt1 = document.querySelector("#ipt1");
      //设定一个变量,用来存储定时器
      let timer = null
      ipt1.onkeydown = function(e){
      //如果之前有定时器的话就取消定时器
        timer && clearTimeout(timer);
        //将延迟0.5s的定时器保存到timer里面
        timer = setTimeout(()=>{
          console.log(timer);//1,2,3...
          console.log(e.target.value);
        },500)
      }

让我们来看一下具体过程:

  1. 第一次触发onkeydown时,开启了一个0.5s的a定时器
  2. 如果在0.5s触发第二个onkeydown,那么a定时器被取消, 触发b定时器(新的定时器)
  3. 如果第三个onkeydown在0.5内触发(b定时器开启之后的.5s内),那么重复No.2步骤;如果第三个onkeydown在0.5s之后触发,那么久会触发事件执行

防抖-非立即执行

  • 那么,简易的完成了,就来封装防抖函数。
       let ipt1 = document.querySelector("#ipt1");
      //对防抖函数的封装
      //传入2个参数,一个为keydown的函数,另一个为延迟的时间
      function debounce(fn,wait){
        let timer;
        //用到了闭包的原理
        return function(){
          timer && clearTimeout(timer);
          //如果有函数传过来,再这里收集
          let args = [...arguments];
          //防止this指向错误
          let context = this;
          timer = setTimeout(()=>{
            //用this的显式绑定来运行函数
            fn.apply(this,args)
          },wait)
        }
      }
      //keydown的函数
      function trigger(e){
        console.log(e.target.value);
      }
      let fn = debounce(trigger,500)
      //不传参可以直接这样写,
      ipt1.onkeydown = fn
      //传参的话用一个函数包裹
      ipt1.onkeydown = function(){
        fn('123')
      }

防抖-立即执行

  • 上面的函数分析发现,其实是个非立即执行版本(按下键盘0.5s后再触发)
  • 那么现在来实现一个立即执行版本(按下键盘之后触发)
      function debounce(fn,wait){
        let timer;
        return function(){
          timer && clearTimeout(timer)
          let context = this;
          let args = [...arguments];
          //这里设置了一个变量,取反timer
          let doNow = !timer;
          timer = setTimeout(()=>{
          //定时器里面改变的timer
            timer = null;
          },wait)
          //根据定时器的反变量来决定是否执行函数
          if(doNow){
            fn.apply(context,this)
          }
        }
      }
  • 这里大家可以自己分析一下步骤过程,相信对你理解内部运行有很大的帮助

合并方案

  • 原理:根据immediate变量来选择非立即执行还是立即执行版本
      function finallyDebounce(fn,wait,immediate){
        let timer;
        return function(){
          timer && clearTimeout(timer)
          let args = [...arguments]
          let context = this;
          if(immediate){
            const Now = !timer;
            timer = setTimeout(()=>{
              timer = null;
            },wait)
            if(Now){
              fn.apply(context,args)
            }
          }else{
            timer = setTimeout(()=>{
              fn.apply(context,args)
            },wait)
          }
        }
      }

节流函数

  • 个人理解:防抖和节流其实根据wait的设置可以达到一样效果,但是,防抖一般用来处理,很多触发,最后只触发一次,而节流主要达到再规定的时间内触发一次(可以有n个规定的时间即触发n次)

节流-立即执行

  • 原理:通过时间戳来实现
      function throttle(fn,wait){
        let time = 0
        return function(){
          //选取现在的时间戳
          let now = Date.now();
          let args = [...arguments];
          let context = this;
          //因为now-time = now肯定大于wait
          //所以函数会先执行一次
          if(now - time > wait){
            fn.apply(context,args)
            time = now
          }
        }
      }

节流-非立即执行

      function throttle(fn,wait){
        let timer;
        return function(){
          let args = [...arguments];
          let context = this;
          //为了实现在规定的时间内必须执行一次!
          if(!timer){
            timer = setTimeout(()=>{
              fn.apply(context,this)
              timer = null
            },wait)
          }
        }
      }

双剑合并

      function finalyThrottle(fn,wait,immediate){
        if(immediate){
          let time = 0;
          return function(){
            let args = [...arguments];
            let context = this;
            let now = Date.now()
            if(now - time > wait){
              fn.apply(context,args)
              time = now;
            }
          }
        }else{
          let timer
          return function(){
            let args = [...arguments];
            let context = this;
            if(!timer){
              timer = setTimeout(()=>{
                fn.apply(context,args);
                timer = null;
              },wait)
            }
          }
        }
      }