前一段在看underscore的源码时,其中有一个函数throttle,就是函数节流:
(1) 函数被频繁调用的场景
window.onresize 事件。我们给 window 对象绑定了 resize 事件,当浏览器窗口大小被拖动
而改变的时候,这个事件触发的频率非常之高。如果我们在 window.onresize 事件函数里
有一些跟 DOM 节点相关的操作,而跟 DOM 节点相关的操作往往是非常消耗性能的,这
时候浏览器可能就会吃不消而造成卡顿现象。
mousemove 事件。同样,如果我们给一个 div 节点绑定了拖曳事件(主要是 mousemove) ,当
div 节点被拖动的时候,也会频繁地触发该拖曳事件函数。
上传进度。微云的上传功能使用了公司提供的一个浏览器插件。该浏览器插件在真正开
始上传文件之前,会对文件进行扫描并随时通知 JavaScript 函数,以便在页面中显示当前
的扫描进度。但该插件通知的频率非常之高,大约一秒钟 10 次,很显然我们在页面中不
需要如此频繁地去提示用户。
(2) 函数节流的原理
我们整理上面提到的三个场景,发现它们面临的共同问题是函数被触发的频率太高。
比如我们在 window.onresize 事件中要打印当前的浏览器窗口大小,在我们通过拖曳来改变
窗口大小的时候,打印窗口大小的工作 1 秒钟进行了 10 次。而我们实际上只需要 2 次或者 3 次。
这就需要我们按时间段来忽略掉一些事件请求,比如确保在 500ms 内只打印一次。很显然,我们
可以借助 setTimeout 来完成这件事情。
(3)underscore的实现
throttle_.throttle(function, wait, [options]) 创建并返回一个像节流阀一样的函数,当重复调用函数的时候,至少每隔 wait毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。(愚人码头注:详见:javascript函数的throttle和debounce,感谢 @澳利澳先生 的翻译建议) 默认情况下,throttle将在你调用的第一时间尽快执行这个function,并且,如果你在wait周期内调用任意次数的函数,都将尽快的被覆盖。如果你想禁用第一次首先执行的话,传递{leading: false},还有如果你想禁用最后一次执行的话,传递{trailing: false}。 var throttled = _.throttle(updatePosition, 100); $(window).scroll(throttled); 源码: _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; };
(4)js实现
var throttle = function ( fn, interval ) {
var __self = fn, // 保存需要被延迟执行的函数引用
timer, // 定时器
firstTime = true; // 是否是第一次调用
return function () {
var args = arguments,
__me = this;
if ( firstTime ) { // 如果是第一次调用,不需延迟执行
__self.apply(__me, args);
return firstTime = false;
}
if ( timer ) { // 如果定时器还在,说明前一次延迟执行还没有完成
return false;
}
timer = setTimeout(function () { // 延迟一段时间执行
clearTimeout(timer);
timer = null;
__self.apply(__me, args);
}, interval || 500 );
};
};
window.onresize = throttle(function(){ console.log( 1 ); }, 500 );
(6)函数去抖
debounce 时间间隔 t 内若再次触发事件,则重新计时,直到停止时间大于或等于 t 才执行函数
_.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { var last = _.now() - timestamp; if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; };
(7)加一个函数柯里化
意思:currying 又称部分求值。一个 currying 的函数首先会接受一些参数,接受了这些参数之后,
该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保
存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值
var currying = function( fn ){
var args = [];
return function(){
if ( arguments.length === 0 ){
return fn.apply( this, args );
}else{
[].push.apply( args, arguments );
return arguments.callee;
}
}
};
var cost = (function(){
var money = 0;
return function(){
for ( var i = 0, l = arguments.length; i < l; i++ ){
money += arguments[ i ];
}
return money;
}
})();
var cost = currying( cost ); // 转化成 currying 函数
cost( 100 ); // 未真正求值
cost( 200 ); // 未真正求值
cost( 300 ); // 未真正求值
alert ( cost() ); // 求值并输出: 600
:本文的代码摘自《设计模式与开发实践》一书。好书,建议大家看看。