underscore是一個非常不錯的基礎(chǔ)javascript庫,他提供了很多實用的方法,彌補了javascript原生 API調(diào)用的一些不足,讀他的文檔的時候,讀到array,object,uitlity的時候已經(jīng)非常興奮了。覺得足夠用了。但是我看到function這部分的時候,發(fā)現(xiàn)這些函數(shù)真的非常的有意義,結(jié)合源代碼來看看這部分function相關(guān)的功能。
_.bind方法
最常見的方法。作用是改變默認(rèn)的function中的this指向。需要說明的是在ECMA 5這個版本中function已經(jīng)自帶了一個bind方法,參見
這里(該文章具體介紹了bind的集中使用場景)。bind的使用方法是:
_.bind(function, object, [*arguments])
下面是一個使用demo:
var func = function(greeting){
//this指向的是bind的第二個參數(shù)
// greeting 是bind的第三個參數(shù)
return greeting + ': ' + this.name
};
// bind返回的是一個新的function對象
var newfunc = _.bind(func, {name : 'moe'}, 'hi');
func();
原本以為這個bind的源碼會很簡單,無非就是用apply返回新的function,但是看源碼發(fā)現(xiàn)挺講究:
_.bind = function bind(func, context) {
var bound, args;
//如果function存在原生的bind方法使用原生的bind
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
//不是function,拋異常
if (!_.isFunction(func)) throw new TypeError;
//將后面的參數(shù)轉(zhuǎn)化成數(shù)組
args = slice.call(arguments, 2);
return bound = function() {
//如果當(dāng)前的this已經(jīng)指向的一個function的實例,就不需要再改變this的指向,因為此時的function已經(jīng)作為一個構(gòu)造函數(shù)在使用
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
//否則function視為構(gòu)造函數(shù)使用,要保證this為構(gòu)造函數(shù)的實例
ctor.prototype = func.prototype;
var self = new ctor;
//將function強制轉(zhuǎn)換成一個類的構(gòu)造函數(shù)
var result = func.apply(self, args.concat(slice.call(arguments)));
//Object(result) === result 只有當(dāng)result是Object時才會成立,基本的數(shù)據(jù)類型如number,string則不成立
if (Object(result) === result) return result;
return self;
};
};
代碼實現(xiàn)還是考慮了普通函數(shù)調(diào)用,構(gòu)造函數(shù)調(diào)用,通過成員函數(shù)調(diào)用的情況,邏輯實現(xiàn)的很全面。
_.bindAll
bindAll方法可以將一個對象中所有的成員函數(shù)的this都指向這個對象,什么情況下對象的成員函數(shù)的this不指向?qū)ο竽兀勘热纾?/p>
var buttonView = {
label : 'underscore',
onClick : function(){ alert('clicked: ' + this.label); },
onHover : function(){ console.log('hovering: ' + this.label); }
};
_.bindAll(buttonView);
//當(dāng)成員函數(shù)作為事件監(jiān)聽的時候,因為默認(rèn)的事件監(jiān)聽,this都會指向當(dāng)前事件源
//bindAll之后可以保證onClick中的this仍指向buttonView
jQuery('#underscore_button').bind('click', buttonView.onClick);
_.memoize(function, [hashFunction])
該方法可以緩存函數(shù)返回結(jié)果,如果一個函數(shù)計算需要很長的時間,多次反復(fù)計算可以只計算一次緩存結(jié)果,默認(rèn)的緩存key是函數(shù)調(diào)用時的第一個參數(shù),也可以自己定義function(第二個參數(shù))來計算key
_.memoize = function(func, hasher) {
var memo = {};//緩存存放位置
//_.indentity默認(rèn)取數(shù)組第一個元素
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
delay : _.delay(function, wait, [*arguments])
delay方法在指定的wait后面執(zhí)行函數(shù)與setTimeout功能一致
defer: _.defer(function, [*arguments])
defer也是延遲執(zhí)行方法,不同的是他能保證在當(dāng)前堆棧中的所有的代碼跑完之后再執(zhí)行function。其實就是setTimeout(fn,1);
throttle:_.throttle(function, wait)
throttle這個單詞的意思是使減速,用于控制頻繁觸發(fā)的 function的的頻率,比如,拖動頁面滾動條時scroll方法會以很高的頻率觸發(fā),如果在scroll的處理事件中做了很費時的操作,會導(dǎo)致瀏覽器假死,如果使用了throttle后,function被觸發(fā)的頻率可以降低。
document.body.onscroll = _.throttle(function(){
console.log("scrolling:"+(document.body.scrollTop|| document.body.scrollTop);
},100);
scroll事件默認(rèn)50ms觸發(fā)一次,但是使用throttle之后事件觸發(fā)頻率為100ms一次
debounce: _.debounce(function, wait, [immediate])
debounce 本意是“使反跳”,這個翻譯是在讓人看不明白。同樣用于處理頻繁觸發(fā)的事件,處理方法時,對于頻繁處理的時間,只在第一次觸發(fā)(是否觸發(fā)取決于immdiate 參數(shù)),和事件頻繁觸發(fā)最后一次觸發(fā)(有最多wait的延時)。拿滾動事件為例,滾動事件50ms觸發(fā)一次,如果設(shè)置wait為100ms。則在最后一次觸發(fā)scroll事件時,也就是停止?jié)L動時,在100ms后觸發(fā)function。如果immediate參數(shù)為true,開始滾動時也會觸發(fā)function
document.body.onscroll = _.debounce(function(){
//一次滾動過程觸發(fā)兩次該函數(shù)
console.log("scrolling:"+(document.body.scrollTop|| document.body.scrollTop);
},100,true);
在整個滾動過程中觸發(fā)function,對于只關(guān)注整個滾動前后變化的處理非常有用。
下面是_.debounce和_.throttle的源碼:
_.debounce = function(func, wait, immediate) {
var timeout, result;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;//最后一次調(diào)用時清除延時
if (!immediate) result = func.apply(context, args);
};
var callNow = immediate && !timeout;
//每次func被調(diào)用,都是先清除延時再重新設(shè)置延時,這樣只有最后一次觸發(fā)func再經(jīng)過wait延時后才會調(diào)用func
clearTimeout(timeout);//
timeout = setTimeout(later, wait);
//如果第一次func被調(diào)用 && immediate ->立即執(zhí)行func
if (callNow) result = func.apply(context, args);
return result;
};
};
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more, result;
//延時wait后將more throttling 設(shè)置為false
var whenDone = _.debounce(function(){
more = throttling = false;
}, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) { //more:最后一次func調(diào)用時,確保還能再調(diào)用一次
result = func.apply(context, args);
}
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
//每次觸發(fā)func 有會保證throttling 設(shè)置為true
throttling = true;
result = func.apply(context, args);
}
//每次觸發(fā)func 在 wait延時后將 more throttling 設(shè)置為false
whenDone();
return result;
};
};
once: _.once(function)
once能確保func只調(diào)用一次,如果用func返回一個什么對象,這個對象成了單例。源碼也比較簡單,無非就是用一個標(biāo)志位來標(biāo)示是否運行過,緩存返回值
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
memo = func.apply(this, arguments);
func = null;
return memo;
};
};
wrap: _.wrap(function, wrapper)
wrap可以將函數(shù)再包裹一層,返回一個新的函數(shù),新的函數(shù)里面可以調(diào)用原來的函數(shù),可以將原函數(shù)的處理結(jié)果再處理一次返回。類似與AOP切面。在函數(shù)處理前/后動態(tài)的添加一些額外的處理,下面是一個使用demo
var hello = function(name) { return "hello: " + name; };
//wrap返回一個新的函數(shù)
hello = _.wrap(hello, function(func) {
// 在新函數(shù)內(nèi)部可以繼續(xù)調(diào)用原函數(shù)
return "before, " + func("moe") + ", after";
});
hello();
wrap的源碼:
_.wrap = function(func, wrapper) {
return function() {
//將原函數(shù)當(dāng)新函數(shù)的一個參數(shù)傳入
var args = [func];
push.apply(args, arguments);
return wrapper.apply(this, args);
};
};
compose: _.compose(*functions)
將多個函數(shù)處理過程合并,每個函數(shù)可以調(diào)用前面函數(shù)的運行結(jié)果,_.compose(func1,func2);相當(dāng)于func1(func2())。看看他的源碼:
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
//循環(huán)調(diào)用參數(shù)中的function
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];//先調(diào)用的函數(shù)結(jié)果最為下一個函數(shù)的參數(shù)
};
};
after:_.after(count, function)
創(chuàng)建一個新的函數(shù),當(dāng)func反復(fù)調(diào)用時,count次才調(diào)用一次,比如:
function a(){
alert("a");
}
var afterA = _.after(3,a);
afterA();//調(diào)用
afterA();//不alert
afterA();//不alert
afterA();//調(diào)用
源碼:
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
if (--times < 1) {
return func.apply(this, arguments);
}
};
};
總結(jié):
上面這些函數(shù)在開發(fā)中經(jīng)常能用到,能解決很多特定的問題。undercore的源碼也看得出非常老道,可以非常好的學(xué)習(xí)資料。
參考資料:
ES5中的bind介紹
從underscore.js的源碼學(xué)習(xí)javascript
帶注釋的underscore源碼