throttle/debounce: 为你的cpu减减压(前端性能优化)

何为throttle, 何为debounce?

谷歌翻译给出的意思:throttle 掐死???   debounce 去抖  

好吧,按理解我们习惯翻译成 ——节流。

那么在什么场景下需要用到?

场景一:onresize,onscroll,onmousemove

场景二:input,autocomplete

如果我们什么都不做,浏览器将会频繁的调用你绑定的事件,如果电脑配置低就会感觉卡滞,也就是你的应用性能太差。

入门级写法:——拖动就fire注册的事件

function onResize(){
    console.log('log');
};
window.addEventListener('resize',onResize,false)

进阶级写法:——等你安静了我再调用

var timer = null;
function onResize(){      
    clearTimeout(timer);
    timer = setTimeout(function(){
        console.log('log');
    },200);
};
window.addEventListener('resize',onResize,false)

这里有个问题:引入了变量timer,它其实属于onResize的,但是你不能放到onResie里面。这样写实在是太丑了,得想办法。

如果我们把这个变量注册为onResize的属性,变成:

function onResize(){
    clearTimeout(onResize.timer);  //this is difference
    onResize.timer = setTimeout(function(){
        console.log('log');
    },200);
};
window.addEventListener('resize',onResize,false)    

插句:这种方法在proxy中用处比较大。大家都知道要移除注册给dom的事件,需要指向一致,这个proxy就可以派上用场了。i.e,仅仅是个栗子!

Contextmenu.prototype = {
        proxy: function(fn){
            if(!fn._contextmenu_proxy){
                fn._contextmenu_proxy = $.proxy(fn, this);
            }
            return fn._contextmenu_proxy;
        },
        init:function(dialModel,dialView){
            var me = this;
            $document.on('contextmenu',me.proxy(me.onContextMenu));
       }
}

再进阶级写法:——封装

网上漫天叫嚣着“面向对象,封装,继承……” 你要不封装一个,还面向过程 是不是会喷死?

上面我们其实已经实现了基础的函数节流。但我们不能每次都要写同样的逻辑吧。所以抽象一个公用函数来做这件事,具体可以这样实现。

jquery是这样做的,google 能搜到$.throttle,但是为啥我在源码里没搜到???

underscore.js是这样实现的:

_.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    options || (options = {});
    var later = function() {
      previous = options.leading === false ? 0 : new Date;
      timeout = null;
      result = func.apply(context, args);
    };
    return function() {
      var now = new Date;
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0) {
        clearTimeout(timeout);
        timeout = null;
        previous = now;
        result = func.apply(context, args);
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

  // Returns a function, that, as long as it continues to be invoked, will not
  // be triggered. The function will be called after it stops being called for
  // N milliseconds. If `immediate` is passed, trigger the function on the
  // leading edge, instead of the trailing.
  _.debounce = function(func, wait, immediate) {
    var timeout, args, context, timestamp, result;
    return function() {
      context = this;
      args = arguments;
      timestamp = new Date();
      var later = function() {
        var last = (new Date()) - timestamp;
        if (last < wait) {
          timeout = setTimeout(later, wait - last);
        } else {
          timeout = null;
          if (!immediate) result = func.apply(context, args);
        }
      };
      var callNow = immediate && !timeout;
      if (!timeout) {
        timeout = setTimeout(later, wait);
      }
      if (callNow) result = func.apply(context, args);
      return result;
    };
  };

调用:

function updatePosition(){
    //console.log('s');
};
//默认立即执行第一次,如果不想要立即执行,可以这样
_.throttle(updatePosition, 500,{leading:false});
//默认当你停止操作时会延时执行最后一次,如果不想要,可以这样   _.throttle(updatePosition, 500,{trailing:false});

var throttled = _.throttle(updatePosition, 500);
$(window).scroll(throttled);

//debonce——debonce的意思,打个比方:你乘电梯经常碰到快要关门时有人要上,电梯得再次开了让人上来。这个过程可能反复好几次,如果我们一致等着,等到一个时间间隔没人时再关门,是不是方便很//多,不太形象,大概也能明白什么意思

function calculateLayout(){
   console.log('resize');
};

//如果想要立即执行,传入参数true  _.debounce(calculateLayout, 2000,true);
var lazyLayout = _.debounce(calculateLayout, 2000);
$(window).resize(lazyLayout);

baidu Tangram  

貌似没看到??

好吧,我们来实现一个简单的封装,不需要那么复杂,好理解的版本

 

/*
        *author@http://wuya1234.github.io
*思路: 1.之前已经说过,可以用setTimeout延时处理太频繁的操作 2.但是我们不想让操作过程一直没有反应,所以需要指定比如:每隔200毫秒不管怎样都执行一次
*/ function throttle(fn,delay,mustRun){ var timer, t_start, previous; return function(){ var arg = arguments, scope = this; var now = +new Date; var defer = function(){ previous = now; fn.call(scope,arg); }; var diff = now - previous - delay; if(diff <= 0){ clearTimeout(timer); timeout = timer; previous = now; fn.call(scope,arg); }else{ timer = setTimeout(fn.call(scope,arg),diff); } }; }; function onResize(){ clearTimeout(onResize.timer); onResize.timer = setTimeout(function(){ console.log('log'); },200); }; window.addEventListener('resize',throttle(onResize),false);

更多相关知识可以到这些地方看看:

1.http://remysharp.com/2010/07/21/throttling-function-calls/

3.http://www.css88.com/archives/5256#more-5256

4.http://www.alloyteam.com/2012/11/javascript-throttle/

posted @ 2013-11-11 17:52  城池  阅读(1793)  评论(1编辑  收藏  举报