美文网首页
一句话说清楚_.throttle和_.debounce的区别

一句话说清楚_.throttle和_.debounce的区别

作者: mocobk | 来源:发表于2020-06-12 22:31 被阅读0次

    lodash中文文档

    underscore里面会有两个防止多次重复操作的方法,_.throttle和_.debounce,它很像setTimout的升级版,但是它们到底有什么区别呢?

    我们来看下用setTimout实现的一段代码:

    var timer$(window).on("click", () => {
      if(timer) {
        clearTimeout(timer)
      }
      
      timer = setTimeout(() => {
        // ...
      }, 500)
    })
    

    通过上面这段代码,可以防止反复点击窗口反复执行同一个函数。这个方法被称为“防抖”。使用_.debounce可以更优雅的实现:

    $(window).on("click", _.debounce(() => {
      // ...
    }, 500))
    

    明显减少了非常多的代码。但是上面的这种方法会有一个问题,如果在500毫秒内一直点击,那么函数永远只会执行一次,点击不停止,函数就不会再执行。一般来说不会有这种情况出现,但是有的时候确实需要有这种机制:在不断点击的时候,如果距离上次执行函数超过了500毫秒,那么就可以再执行函数一次。

    我们再来看_.throttle又是什么情况:

    var flag = false$(window).on("click", () => {
      if(flag) return
      flag = true
      // ...
      setTimeout(() => flag = false, 500)
    })
    

    上面这段代码通过一个flag变量来记录当前的点击状态,第一次点击之后会执行动作,但是500毫秒内再点击不会执行,直到过了500毫秒,再点击就有效,如此循环下去。

    简便的写法是:

    $(window).on("click", _.throttle(() => {
      // ...
    }, 500))
    

    这种方法会让第一次点击之后500ms之后的点击再生效。这种方法被称为“节流”。

    从上面可以看出来,_.throttle和_.debounce的区别:

    虽然在等待时间内函数都不会再执行,但_.throttle在第一次触发后开始计算等待时间,_.debounce在最后一次触发之后才计算等待时间(最后一次在等待时间范围内)。

    有一个点需要特别注意,_.throttle和_.debounce的返回值是一个函数,运行这个函数才相当于运行包装了setTimeout的函数体,它的大致结构如下:

    _.debounce = function(factory, time) {
      var timer
      return function() {
        if(timer) clearTimeout(timer)
        timer = setTimeout(factory, time)
      }
    }
    // for example:
    function someClass() {}
    someClass.prototype.action = _.debounce(function() { // action将会是一个函数
      this.anotherAction() // 注意这里直接使用this,this指向someClass的实例化对象
    }, 500);
    

    debounce使用中应该要注意哪些问题?

    var fun = _.debounce(factory, wait, immediate)
    

    如果immediate设置为true,表示当执行fun()时,factory马上执行,并且在wait这个时间段内即使再执行fun(),将不再执行factory函数,而且计时器还会被重置为0,重新计时,而如果计时器过了wait设定的时间,在执行fun()时,就会再触发factory。

    var action = function() {
        alert("ok")
    }
    var deb = _.debounce(action, 2000, true)
    deb() // 立即执行了action()
    deb() // 啥也不做,计时器被重置为0,重新开始
    setTimeout(deb, 1000) // 啥也不做,计时器被重置为0,重新开始
    setTimeout(deb, 3001) // 从上一次执行deb之后过了2001毫秒,所以action()再次被执行
    

    而如果将immediate设置为false,表示当执行fun()时,开始等待,过了wait时间段之后立即执行factory这个函数,但是如果中途再次执行fun()的话,计时器将被重置为0,并继续计时到wait之后才执行factory。

    var action = function() {
        alert("ok")
    }
    var deb = _.debounce(action, 2000)
    deb() // 啥都不做
    deb() // 啥也不做,计时器被重置为0,重新开始
    setTimeout(deb, 1000) // 啥也不做,计时器被重置为0,重新开始
    setTimeout(deb, 3001) // 从上一次执行deb之后过了2001毫秒,所以action()终于被执行了
    

    第一种用法主要用在防止短时间内被触发多次,比如有一个请求,你只允许它在一组操作完成之后才发出,这样可以解决服务器的请求压力,而在前端,则用_.debounce来控制,第一次发出请求之后,必须等到2秒之后再点某个按钮才会发出请求,而在这期间点击按钮,相当于阻止立即发出,重新再等2秒。

    而第二种则主要用在延时操作上面,比如有一组按钮,当按钮状态发生变化之后才发请求,用户点第一个按钮的时候触发deb(),点第二个按钮之后再触发一次deb(),这个时候计时器被重置为0,请求不会马上发出去,直到用户点完最后一个按钮之后,等了0.2秒钟,请求才发出去。

    而_.throttle主要用在点击多次后不会马上执行,而是将这些执行按平均每wait的时间只执行一次,immediate设置为true和false的区别是,为true时点击之后马上执行,为false时点击之后等待wait后开始执行,后面就没有影响,永远都是隔wait时间后再执行一次,点多少次,执行多少次。用在scroll身上比较好,因为scroll会一直一直触发,而且不规律,而使用_.throttle则可以让这些scroll规律化,实现滚动条平滑滚动。

    不要作为类的方法

    当我们写一个类的时候,把_.debounce作为方法,会带来实例化对象之间的计时器混乱。说的简单,请拿代码给我看:

    function DebounceTest() {}
    DebounceTest.prototype.request = _.debounce(function(tag) {
        console.log(tag, Date.now())
    }, 2000, true)
    var test1 = new DebounceTest()
    var test2 = new DebounceTest()
    test1.request(1)
    test2.request(2)
    

    你会发现,test2的request没有执行,这是一个不期望得到的结果。你的设想是,通过new来实例化的对象,各自有各自的debounce,test1是test1,test2是test2.但是实际上,因为我们采用的是prototype进行赋值的方式,也就是说DebounceTest.prototype.request是_.debounce执行后得到的结果,而这个结果绑定在了prototype上,那么所有的实例化对象的request方法都引用同一个_.debounce的结果,所以上面的test1.request和test2.request虽然是不同实例化对象的操作,却是对同一个_.debounce的结果函数进行执行。因此,不要将_.debounce的结果作为类的方法使用。

    参考文献:

    原文: https://www.tangshuang.net/3133.html

    相关文章

      网友评论

          本文标题:一句话说清楚_.throttle和_.debounce的区别

          本文链接:https://www.haomeiwen.com/subject/nfkotktx.html