手写防抖与防抖Plus

开发中有一些页面的节点绑定的事件会被频繁触发,比如鼠标移动 onmousemove,窗口的resize,scroll,输入框的改变,我们希望他触发时有效果但又不希望被频繁的触发影响性能,这就是我们的初衷

先看个小案例:


<body>
  <!-- 一个盒子 用于展示数字 -->
  <div id='content'></div>
</body>
<script>
  let count=1//计数变量
  const dom =document.getElementById('content')
  // 鼠标移动的绑定事件 只要移动就count++
  handleMove=(e)=>{
    e.target.innerHTML=count++
  }
  dom.onmousemove=handleMove
</script>

未防抖
可以看到我鼠标移动的短暂几秒,计数器就从1加到了260,也就是回调函数在这几秒钟执行了260次,这种情况浏览器是吃的消,但是真实业务中事件的回调函数可能会很复杂,也可能是发送了ajax请求,这样频繁得被执行可能会造成页面卡顿。性能下降。这时就有两种主要的解决方法:防抖(debounce ),节流(throttle )

下面来通过防抖改进一下这个案例

防抖的思路:将回调函数放入一个定时器中经过指定的时间后执行,如果这段时间内又触发了事件,那么就将之前的定时器给取消,重新开始计时;所以说防抖的最终结果就是不论你触发多少次,我最终只执行最后一次

按照这个思路来写一下代码:


<script>
  let count=1//计数变量
  const dom =document.getElementById('content')
  // 鼠标移动的绑定事件 只要移动就count++
  handleMove=(e)=>{
    e.target.innerHTML=count++
  }
  //防抖函数,传两个参数 第一个是事件绑定的回调函数,
  //第二个是指定的延迟时间
  debounce=(callback,waitTime)=>{
    let timeout
    return function(){
      clearTimeout(timeout)
      timeout=setTimeout(callback,waitTime)
    }
  }
  //注意这里是将防抖函数的结果作为新的回调函数
  dom.onmousemove=debounce(handleMove,500)
</script>

去浏览器看一下结果,发现防抖函数返回的回调并没有被执行,而且浏览器还报了个错:

在这里插入图片描述
也就是说,现在的事件对象e已经找不到了,可以想到,e是传给了新的回调函数,也就是12行返回的那个函数,但我并没有把他传给callback,那我是不是获取到参数传给callback就够了呢?
其实还是不行的,因为我们现在的函数还存在一个问题就是,callback的this指向已经发生变化,最初没有使用防抖的时候,handlMove指向的this是他绑定的dom节点,但是当我们放在定时器里时,this就会指向window
所以综上两点改进方向,1.保留this指向给callbck 2.把参数传给callback
那我们很容易想到使用bind 看一下新的debounce函数吧~


//防抖函数,传两个参数 第一个是事件绑定的回调函数,
//第二个是指定的延迟时间
  debounce=function(callback,waitTime){
    let timeout
    return function(){
      let args=arguments//arguments类数组的第一项就是事件对象
      const self =this//把this保留下来
      clearTimeout(timeout)
      timeout=setTimeout(callback.bind(self,...args),waitTime)
    }
  }

看一下我们的成果~

在这里插入图片描述

nice~现在无论怎么移动鼠标,他也只会在我停下来的0.5s后触发回调了

好运不长(ಥ﹏ಥ),现在需求变了,要求页面我们中的这个回调函数,一触发就可以执行,但n秒内不会再执行,也就是把执行回调时间从结束放到了开始

防止需求一直变更!(好听点就叫可复用易维护)我们可以在防抖函数中,加上第三个参数immediate,如果是true 代表需要第一次触发就执行,如果是false第一次就不执行,而是放到最后执行,保持之前的思路


debounce=function(callback,waitTime,immediate){
  let timeout
  return function(){
    let args=arguments//arguments类数组的第一项就是事件对象
    const self =this//把this保留下来
    if(timeout) clearTimeout(timeout)
    if(immediate){
      let callNow=!timeout//新设立一个变量与timeout相反 
      timeout=setTimeout(() => {
        timeout=null //保证n秒之后还能再触发
      }, waitTime);
      if(callNow) callback(...arguments)
    }else{
      timeout=setTimeout(callback.bind(self,...args),waitTime)
    }
  }
}

我现在给debouce函数第三个参数传入true

  dom.onmousemove=debounce(handleMove,1000,true)

看一下结果吧~
在这里插入图片描述
很好 实现成功~

新的业务需求又来了 万恶的甲方!(〃>皿<)
我们现在immediate设置为true,他第一次执行一定要等到n秒之后才能再次触发,甲方现在等不及了,说你给我一个按钮,我点击了之后,可以取消计时,马上就能触发执行

╮(╯▽╰)╭那就改代码咯~


debounce=function(callback,waitTime,immediate){
  let timeout
  //和刚刚的区别就是我给返回的函数取了个名字
  //这样我方便给返回的函数添加一个cancle的方法属性,用来取消
  const _debounce=function(){
    let args=arguments//arguments类数组的第一项就是事件对象
    const self =this//把this保留下来
    if(timeout) clearTimeout(timeout)
    if(immediate){
      let callNow=!timeout//新设立一个变量与timeout相反 
      timeout=setTimeout(() => {
        timeout=null //保证n秒之后还能再触发
      }, waitTime);
      if(callNow) callback(...arguments)
    }else{
      timeout=setTimeout(callback.bind(self,...args),waitTime)
    }
  }
  //只要cancel函数一运行,我就取消计时,同时让timeout为Null
  //这样才能保证点击了之后马上运行
  _debounce.cancel=function(){
    if(timeout) clearTimeout(timeout)
    timeout=null
  }
  return _debounce
}

那我们只要给button绑定点击事件就好,一点击 就触发cancel函数


let handle=debounce(handleMove,10000,true)
dom.onmousemove=handle
//
btn.onclick=()=>{
  handle.cancel()
}

现在看一下执行效果吧~

在这里插入图片描述
是不是非常的nice~

手写系列之防抖与防抖Plus就到这啦

感谢阅读,欢迎指正~

文章来自于本人公众号,觉得有帮助,感兴趣的话可以关注一下:西元前的小铁匠
在这里插入图片描述

参考:

JavaScript专题之跟着underscore学防抖

github Owner: mqyqingfeng

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值