防抖和节流

一、前言

以下场景往往由于事件频繁被触发,因而频繁执行DOM操作、资源加载等重行为,导致UI停顿甚至浏览器崩溃。

  1. window对象的resize、scroll事件
  2. 拖拽时的mousemove事件
  3. 射击游戏中的mousedown、keydown事件
  4. 文字输入、自动完成的keyup事件

实际上对于window的resize事件,实际需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。

二、什么是debounce

如果用手指一直按住一个弹簧,它将不会弹起直到你松手为止。
也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。
假定在做公交车时,司机需等待最后一个人进入后再关门,每次新进一个人,司机就会把计时器清零并重新开始计时,重新等待 1 分钟再关门,如果后续 1 分钟内都没有乘客上车,司机会认为乘客都上来了,将关门发车。

/**
* 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 wait,fn 才会执行
* @param wait   {number}    空闲时间,单位毫秒
* @param fn {function}  请求关联函数,实际应用需要调用的函数
* @return {function}    返回客户调用函数
*/
debounce(fn,wait)

简单实现

var debounce = function(fn,wait){
  var timer
  return function(){
    var _this = this, args = arguments
    timer && clearTimeout(timer)
    timer = setTimeout(function(){
        fn.apply(_this, args)
    }, wait)
  }
}

立即执行版

function debounce(fn,wait=1000,immediate = true){
   let timer = null;
   return function(){
     let that = this
     let args = arguments;
     if(timer){
       clearTimeout(timer);
     }
     if(immediate && !timer){
       fn.apply(that,args)
     }
     timer = setTimeout(function(){
       fn.apply(that,args)
     },wait)
   }
 }

换一种写法(上面的方法有问题,会多执行一次)

function debounce(fn,wait=1000,immediate = true){
        let timer = null;
        return function(){
          let that = this
          let args = arguments;
          let now = !timer;
          if(timer){
            clearTimeout(timer);
          }
          timer = setTimeout(function(){
            //fn.apply(that,args);
            timer = null  
          },wait)
          if(now){
            fn.apply(that,args)
          }
        }
      }

函数节流(throttle 英[ˈθrɒtl])

function throttle(fn, wait = 1000) {
        let timer = null;
        return function () {
          let that = this;
          let args = arguments;
          if(!timer){
            timer = setTimeout(() => {
              timer = null;
              clearTimeout(timer)
              fn.apply(this,args)
            }, wait);
          }
        };
      }

定时器版本

function throttle(fn, wait = 1000) {
        let last = 0
        return function () {
          let that = this;
          let args = arguments;
          let current = Date.now()
          if( current -  last  > wait){
              fn.apply(this,args);
              last  = current
          }
        };
      }

我们应该可以很容易的发现,其实时间戳版和定时器版的节流函数的区别就是,时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候。
关于节流/防抖函数中 context(this) 的指向解析:
  首先,在执行 throttle(count, 1000) 这行代码的时候,会有一个返回值,这个返回值是一个新的匿名函数,因此 content.onmousemove = throttle(count,1000); 这句话最终可以这样理解:

content.onmousemove = function() {
    let now = Date.now();
    let context = this;
    let args = arguments;
    ...
    console.log(this)
}

到这边为止,只是绑定了事件函数,还没有真正执行,而 this 的具体指向需要到真正运行时才能够确定下来。所以这个时候如果我们把前面的 content.onmousemove 替换成 var fn 并执行 fn fn() ,此时内部的 this 打印出来就会是 window 对象。
  
  其次,当我们触发 onmousemove 事件的时候,才真正执行了上述的匿名函数,即 content.onmousemove() 。此时,上述的匿名函数的执行是通过 对象.函数名() 来完成的,那么函数内部的 this 自然指向 对象。
  
  最后,匿名函数内部的 func 的调用方式如果是最普通的直接执行 func() ,那么 func 内部的 this 必然指向 window ,虽然在代码简单的情况下看不出什么异常(结果表现和正常一样),但是这将会是一个隐藏 bug,不得不注意啊!所以,我们通过匿名函数捕获 this,然后通过 func.apply() 的方式,来达到 content.onmousemove = func 这样的效果。

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

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值