手写防抖和节流

一,区别

防抖:在执行时间内,需要触发的事件会不断的延后。

节流:在执行时间内,固定频率触发事件。

二,防抖

理解:当用户频繁操作时,比如输入,不每输一个字母就去请求一次网络,而是在用户停下操作的一段固定时间后,再请求网络,如果这段固定的时间内用户又操作了,那么这段时间清空,网络请求也会被推迟,这样可以减少请求次数,减轻服务器压力。

防抖函数主要是对事件触发频率非常高的函数,对它来进行一个优化,相当于一个性能优化的一部分。

1.原理

利用了定时器闭包的不回收机制原理。在一段时间后执行响应事件函数,如果在这段时间内再次被调用,那么会清理前一次的定时器,重新开始计算执行时间,直到在响应时间内没有再次调用该函数时,则执行响应事件,就是只执行了最后一次调用

2.应用场景

  • 输入框中频繁的输入内容,搜索或提交信息;
  • 频繁点击按钮,触发某个事件;
  • 监听浏览器滚动事件,完成某些特定的操作;
  • 用户缩放浏览器的resize事件等。

3.实现

(1)基础实现

封装一个函数时,1.要考虑需要接收什么参数,基础实现防抖接收参数一:回调的函数,参数二:延迟时间;2.有什么返回值,这里返回一个函数;3.内部实现。

整体思路是拿到上一次的timeout。

根据防抖原理,只执行最后一次操作,需要清理之前的操作。也就是第二次操作时,需要拿到第一次操作时定时器的timeout,但是timeout不能定义在return的函数中,因为这样的话它就是一个局部变量,局部变量在执行完这个函数之后,如果没有一个对应的引用值,那它就会被销毁,所以,在这个函数的外部定义timeout,在函数内部引用外部的timeout变量,会形成闭包,只要debounce()函数不销毁,那么这个闭包也会一直存在。而这个函数绑定在了onclick上,是不会被销毁的。

注意:debounce函数是个独立调用,所以它的this是指向window,所以return的那个函数不要使用箭头函数,箭头函数没有自己的this,那么就会沿着作用域链往上找,找到debounce函数的this,这样的话获取不到当前dom的属性。所以用function(){}的话,返回的这个函数就会绑定到onclick方法上,那么它的this就会指向这个dom。

this指向就是谁调指谁。

(2)优化实现

优化一:响应事件函数的this的指向

dom节点在触发事件的时候,this应该是要指向dom本身(如button)才能获取到某些值,比如this.value,获取input输入框的值,而在基础实现中,事件绑定时该响应事件函数执行时是独立函数(func()),this的指向是window(如下图),所以需要优化改变this的指向。

根据基础实现可知,返回的匿名函数中的this指向button,(12月14日修改:可以不用把this存到that中,因为setTimout那里使用的是箭头函数,下图多了一步,懒得改了哈。)

改变this指向:apply,call,bind,new.

***当setTimeOut使用的是箭头函数时,箭头函数没有自己的this(但是箭头函数在创建的时候会绑定一个this),所以会沿着作用域链向上寻找this。

优化二:获取event参数

12月14号添加说明:下图中使用的是arguments,其实可以直接写return function(...args),使用剩余参数,是个数组。

优化以上两点在面试中写出来已经很好了,以下优化作为补充学习。

优化三:增加一个取消功能

场景:当用户在输入框输入时点击了其它功能,这个操作时间在防抖设定的时间之内,此时已经切换页面了,那么响应函数也不应该再调用,所以需要一个取消定时器功能。

优化四:是否立即执行

编辑

优化五:拿到返回结果

直接引用了封装好的文件

优化完成,封装好的防抖函数:

function debounce (func, wait, immediate = false, resultCallback) {
  let timeout
  let isInvoke = false
  const _debounce = function (...args) {
    return new Promise((resolve, reject) => {
      try {
        if (timeout) clearTimeout(timeout)

        let res
        if (immediate && !isInvoke) {
          res = func.apply(this, args)
          if (resultCallback)resultCallback = res
          resolve(res)
          isInvoke = true
          return
        }
        timeout = setTimeout(() => {
          res = func.apply(this, args)
          if (resultCallback)resultCallback = res
          resolve(res)
          timeout = null
          isInvoke = false
        }, wait)
      } catch (error) {
          reject(error)
      }
    })
  }
  _debounce.cancel = function () {
    if (timeout) {
      clearTimeout(timeout)
      timeout = null
      isInvoke = false
    }
  }
  return _debounce
}

使用:

注意,因为返回的是个函数,所以需要手动调一次。

三,节流

规定的固定时间内就要执行。

1.原理

在执行时间内,固定频率触发事件

2.应用场景

  • 监听页面的滚动事件;
  • 鼠标移动事件;
  • 用户频繁点击按钮操作;
  • 游戏中的一些设计,技能冷却;

3.实现

(1)时间戳实现

等待时间 = 间隔时间 -(当前时间 - 开始时间)

开始时间变量要定义在返回函数外面,这样返回函数里面调用外面的变量,形成闭包,那么这个startTime就不会被销毁。

function throttle(func, interval){
    let startTime = 0
     
    const _throttle = function(..args){
        let nowTime = new Date().getTime() //获取当前时间戳

        //计算需要等待的事件执行函数
        let waitTime = interval - (nowTime - startTime)
        if(waitTime <= 0){
            func.apply(this,args)
            startTime = nowTime
        }
    }
    return _throttle
}

优化一:立即执行

(2)定时器实现

function throttle(func,interval){
  let timer = null
  const _throttle = function(...args){
    if(!timer){
      timer = setTimeout(()=>{
        func.apply(this,args)
        timer = null
      },interval)
    }
  }
  return _throttle
}

四、其它:underscore使用(封装好的库)

CDN引入:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值