美文网首页
JavaScript 节流函数 Throttle

JavaScript 节流函数 Throttle

作者: TingsLee | 来源:发表于2017-06-13 17:04 被阅读0次

    在浏览器 DOM 事件里面,有一些事件会随着用户的操作不间断触发。比如:重新调整浏览器窗口大小(resize),浏览器页面滚动(scroll),鼠标移动(mousemove)。也就是说用户在触发这些浏览器操作的时候,如果脚本里面绑定了对应的事件处理方法,这个方法就不停的触发。

    这并不是我们想要的,因为有的时候如果事件处理方法比较庞大,DOM 操作比如复杂,还不断的触发此类事件就会造成性能上的损失,导致用户体验下降(UI 反映慢、浏览器卡死等)。所以通常来讲我们会给相应事件添加延迟执行的逻辑。

    通常来说我们用下面的代码来实现这个功能:

     var count = 0;
     function test () { count++ }
     window.onresize = function () {
        var timer = null;
        clearTimeout(timer);
        timer = setTimeout(function () {
          test();
        }, 1000);
     }
    

    细心的同学会发现上面的代码其实是错误的,这是新手会犯的一个问题:setTimeout 函数返回值应该保存在一个相对全局变量里面,否则每次 resize 的时候都会产生一个新的计时器,这样就达不到我们发的效果了

     var count = 0,
       timer = null;
     function test () { count++ }
     window.onresize = function () {
        clearTimeout(timer);
        timer = setTimeout(function () {
          test();
        }, 1000);
     }
    

    这时候代码就正常了,但是又多了一个新问题 —— 产生了一个全局变量 timer。这是我们不想见到的,如果这个页面还有别的功能也叫 timer 不同的代码之前就是产生冲突。为了解决这个问题我们要用 JavaScript 的一个语言特性:闭包 closures 。相关知识读者可以去 MDN 中了解,改造后的代码如下:

    var count = 0;
    function test () { count++ }
    
    function throttle (fn, delay) {
      var timer = null;
      return function () {
        clearTimeout(timer);
        timer = setTimeout(fn, delay)
      }
    }
    window.onresize = throttle(test, 1000);
    

    这里主要了解一点:Throttle 被调用后返回的 function 才是真正的 onresize 触发时需要调用的函数
    现在看起来这个方法已经接近完美了,然而实际使用中并非如此。举个例子:

    如果用户 不断的 resize 浏览器窗口大小,这时延迟处理函数一次都不会执行

    于是我们又要添加一个功能:当用户触发 resize 的时候应该 在某段时间 内至少触发一次,既然是在某段时间内,那么这个判断条件就可以取当前的时间毫秒数,每次函数调用把当前的时间和上一次调用时间相减,然后判断差值如果大于 某段时间 就直接触发,否则还是走 timeout 的延迟逻辑。

    下面的代码里面需要指出的是:

    function throttle (fn, delay, atleast) {
      var timer = null;
      var previous = null;
    
      return function () {
        var now = +new Date();
    
        if (!previous) previous = now;
    
        if (now - previous > atleast) {
            fn();
            previous = now;
        } else {
           clearTimeout(timer);
           timer = setTimeout(fn, delay)   
        }
      }
    }
    window.onresize = throttle(function () {console.log(1321)}, 2000, 1000);
    

    如果需要传递参数可以做一下修改:

     function throttle (fn, delay, atleast, context, args) {
      var timer = null;
      var previous = null;
    
      return function () {
        var now = +new Date();
    
        if (!previous) previous = now;
    
        if (now - previous > atleast) {
            fn.call(context, args);
            previous = now;
        } else {
           clearTimeout(timer);
           timer = setTimeout(function () {
             fn.call(context, args);
          }, delay)   
        }
      }
    }
    window.onresize = throttle(function () {console.log(1321)}, 2000, 1000);
    

    引用
    测试代码 http://jsbin.com/tanuxegija/edit
    完整版本代码 http://jsbin.com/jigozuvuko
    Debounce VS throttle https://github.com/dcorb/debounce-throttle

    相关文章

      网友评论

          本文标题:JavaScript 节流函数 Throttle

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