【js笔记】跟着大佬学习之防抖

对于项目优化这块,防抖节流是很重要的一part
在掘金阅读文章-《跟着大佬学防抖》觉得太赞了,自己也想写一篇类似的,和大家一起学习
也因为上述的作者原因,我也去掘金、github上面看大佬的文章,写的真是太好了,下面开启学习之路。
【这篇文章我会大部分跟随作者的步伐,以及加入自己练习的demo进行分析总结】

跟随大佬:冴羽
跟随原文链接: debounce | throttle

debounce(防抖)


前言

在前端开发中会遇到一些频繁的事件触发,比如:

  • windowresizescroll
  • mousedownmousemove
  • keyupkeydown

为此,我们举个示例代码来了解事件是如何地频繁触发的
index.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>debouse</title>
    <style>
        #container{
            width: 100%;
            height: 300px;
            line-height: 300px;
            text-align: center;
            background-color: pink;
            color:#fff;
            font-size: 30px;
            font-weight: bolder;
        }
    </style>
</head>
<body>
    <div id='container'>1</div>
    <script src='debouse.js'></script>
</body>
</html>

debouse.js

var count=1
var container=document.getElementById('container')
function getUserAction(){
    container.innerHTML=count++
}
container.onmousemove=getUserAction

看效果
在这里插入图片描述
从左滑到右(可能我中间有停顿),我是触发了205次getUserAction函数!

这个栗子比较简单,所以浏览器完全反应的过来,可如果是复杂的回调函数或者是ajax呢?【浏览器可能会反应不过来,出现卡顿】

大佬提出了个假设,1秒内触发了60次,每个回调必须在1000/60=16.67ms内完成,否则就会有卡顿出现。【这个是真的比较难,短时间内要求回调完成,就有大概率会出现卡顿】
`

为了解决这个问题,一般有两种解决方案:

  • debounce 防抖
  • throttle 节流

防抖


防抖的实现

防抖的原理:你尽管触发事件,但是我一定在事件触发n秒后才执行,如果你在一个事件触发的n秒内又触发了这个事件,那我们就以新的事件的时间为准,n秒后才执行,总之,就是要等你触发完事件,n秒内不再触发事件,我才执行。

第一版

function debounce(func,wait){
    var timeout;
    return function(){
        clearTimeout(timeout)
        timeout=setTimeout(func,wait)
    }
    }

用到刚才的案例则是:

container.onmousemove=debounce(getUserAction,1000)

随便怎么移动,在1000ms内也不再触发
在这里插入图片描述
从200多次降到一次,现在继续完善


第二版-this

如果我们在getUserAction函数中console.log(this),在不使用debouse函数的时候,this的值为:

  <div id='container'>1</div> 

如果加了debouse之后,this的指向就是window对象,【因为setTimout定义的时候是在window环境下的】

解决方案:

function debounce(func,wait){
    var timeout;
    return function(){
        var context=this
        clearTimeout(timeout)
        timeout=setTimeout(function(){
        //相当于调用了func函数,并且改变了this的指向
            func.apply(context)
        },wait)
    }
    }

加完之后,this的指向就变回调用他的元素


第三版----event对象

js在事件处理函数中会提供事件对象event,我们把它打印出来看一下

function getUserAction(e){
    console.log(e)
    container.innerHTML=count++
}

在有debounce函数的情况下,打印出来的是undefined

But,在没有debounce函数的情况下,打印出来的是MouseEvent对象
在这里插入图片描述
SO,修改代码继续

function debounce(func,wait){
    var timeout;
    return function(){
        var context=this
        var args=arguments
        clearTimeout(timeout)
        timeout=setTimeout(function(){
            func.apply(context,args)
        },wait)
    }
    }

补充:

getUserAction函数里的arguments类数组对象值如图所示

在这里插入图片描述
apply方法
1.第一个参数是改变this的指向
2.第二个参数是数组参数,传递给前面调用它函数所用

上面的代码解决了问题,让函数内可以出现原本的MouseEnter对象


第四版----立即执行

这个时候,代码已经很完善了,但是为了让函数变得更完善,我们还应该思考一个新的需求

这个需求is:

不需要等到事件停止触发后才执行,希望立即执行,然后等到停止触发n秒后,才可以重新触发才行

方法:加一个immediate参数

function debounce(func,wait,immediate){
    var timeout;
    return function(){
        var context=this
        var args=arguments
       if(timeout) clearTimeout(timeout)
        //触发func从队尾提到队前 
        //[remember:func同步执行(同步任务)、setTimeout是异步执行]
        // 1.callNow的初始值是true,同步立即执行,随后timeout开始执行
        //2.wait期间,timeout是一个id数字,所以callNow为false,func在此期间永远不会执行
        //3.wait之后,timeout赋值null,callNow为true,func又立即开始执行
       if(immediate){
        //    如果已经执行过,就不再执行
           var callNow=!timeout
           timeout=setTimeout(function(){
                timeout=null
           },wait)
           if(callNow) func.apply(context,args)
       }
       else{
           timeout=setTimeout(function(){
               func.apply(context,args)
           },wait)
       }
      
    }
    }

效果如下:
在这里插入图片描述


第五版 返回值

此时注意一点,就是getUserAction函数可能是有返回值的,所以我们要返回函数的执行结果,但是当immediatefalse的时候,因为使用了setTimeout,我们将func.apply(context,args)的返回值赋给变量,最后再return的时候,值将会一直是undefined,所以我们只在immediatetrue的时候返回函数的执行结果

function debounce(func,wait,immediate){
    var timeout,result;
    return function(){
        var context=this
        var args=arguments
       if(timeout) clearTimeout(timeout)
        //触发func从队尾提到队前 
        //[remember:func同步执行(同步任务)、setTimeout是异步执行]
        // 1.callNow的初始值是true,同步立即执行,随后timeout开始执行
        //2.wait期间,timeout是一个id数字,所以callNow为false,func在此期间永远不会执行
        //3.wait之后,timeout赋值null,callNow为true,func又立即开始执行
       if(immediate){
        //    如果已经执行过,就不再执行
           var callNow=!timeout
           timeout=setTimeout(function(){
                timeout=null
           },wait)
           if(callNow) {
               result=func.apply(context,args)
           }
       }
       else{
           timeout=setTimeout(function(){
               func.apply(context,args)
           },wait)
       }
      return result
    }
    }

【这个result 是有点难理解的,大佬的解释is:
考虑到func可能会有返回值,虽然平时开发当中比较少接触到,但是作为一个工具库,应该考虑的更全面


第六版-取消

需求:希望能取消debouce函数,比如说debounce的时间间隔是10秒钟,immediate为true,这样的话,我只能等10秒后才能重新触发事件,现在我希望能有一个按钮,点击后取消防抖,这样我再去触发,就可以又立刻执行啦。

function debounce(func, wait, immediate) {
  var timeout, result;
  var debounced = function () {
    var context = this;
    var args = arguments;
    if (timeout) clearTimeout(timeout);
    if (immediate) {
      //    如果已经执行过,就不再执行
      var callNow = !timeout;
      timeout = setTimeout(function () {
        timeout = null;
      }, wait);
      if (callNow) {
        result = func.apply(context, args);
      }
    } else {
      timeout = setTimeout(function () {
        func.apply(context, args);
      }, wait);
    }
    return result;
  };

其他函数部分:

function getUserAction() {
  container.innerHTML = count++;
}
var setUserAction = debounce(getUserAction, 1000, true);

container.onmousemove = setUserAction;
btn.addEventListener("click", function () {
  setUserAction.cancel();
});

在这里插入图片描述
撒花✿✿ヽ(°▽°)ノ✿


演示代码

大佬的github仓库
我的练手github仓库

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值