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/

上一篇:Prism 学习:从配置文件中加载 Module


下一篇:再议“创建类似于输入法窗口的非激活窗口”