underscore源碼分析之基礎方法
本文是underscore源碼剖析系列的第二篇,主要介紹underscore中一些基礎方法的實現。
mixin
在上篇文章underscore整體架構分析中,我們講過_上面的方法有兩種掛載方式,一個是掛載到_構造函數上以_.map(arr)的形式直接調用(在後文上統稱構造函數調用),另一種則是掛到_.prototype上以_(arr).map()的形式被實例調用(在後文上統稱原型調用)。
翻一遍underscore源碼你會發現underscore中的方法都是直接掛到_構造函數上實現的,但是會通過mixin方法來將_上面的方法擴展到_.prototype上面,這樣這些方法既可以直接調用,又可以通過實例來調用。
_.mixin = function(obj) {
// 遍歷obj上所有的方法
_.each(_.functions(obj), function(name) {
// 保存方法的引用
var func = _[name] = obj[name];
_.prototype[name] = function() {
// 將一開始傳入的值放到數組中
var args = [this._wrapped];
// 將方法的參數一起push到數組中(這裏處理的很好,保證了func方法參數的順序)
push.apply(args, arguments);
// 這裏先用apply方法執行了func,並將結果傳給了result
return result(this, func.apply(_, args));
};
});
};
_.mixin(_);
從這段代碼中我們可以看出,mixin方法將_上的所有方法通過遍歷的形式掛載到了_.prototype上面。
細心觀察一下,構造函數調用和原型調用的區別在哪裏?
沒錯,區別就在於調用方式和傳參,構造函數調用時一般會把要處理的值當做第一個參數傳入,而原型調用的時候會把要處理的值傳入_構造函數來創建一個實例。
var arr = [1, 2, 3]
var func = function(item) {
console.log(item);
}
// 構造函數調用時arr被傳入第一個參數
_.each(arr, func)
// 原型調用的時候,arr被當做參數傳給_方法來創建一個實例
_(arr).each(func)
// 鏈式調用,和上面類似
_.chain(arr).each(func)
從上一節中我們知道,在創建一個_的實例時,會用this._wrapped將傳入的值保存起來,所以在mixin裏面這一句:var args = [this._wrapped];是將我們傳給_的值放到args數組第一項中,之後再將arguments也放入args數組中,藉助apply方法執行當前遍歷的方法(在這個例子中是each),這個時候傳給each方法的是arr和func,正好和原來直接_.each調用each傳入參數的順序是一樣的(underscore中的方法第一項基本上都是要處理的數據)。
鏈式調用
那麼上面最後return result(this, func.apply(_, args)),result又是做什麼的呢?
首先來看result源碼:
var result = function(instance, obj) {
// 首先判斷是否使用鏈式調用,如果是,那就繼續將剛剛執行後返回的結果鏈式調用一下,如果不是,則直接返回執行後的結果
return instance._chain ? _(obj).chain() : obj;
};
_.chain = function(obj) {
// 創建一個實例
var instance = _(obj);
// 給這個實例加個_chain屬性來表明這是鏈式調用
instance._chain = true;
return instance;
};
我們知道underscore中也是有和jQuery類似的鏈式調用,來看一下鏈式調用的例子:
var arr = [1, 2, 3]
var newArr = _.chain(a).map(function(item) {
return item + 1
}).filter(function(item) {
return item > 2
}).value()
鏈式調用的關鍵在於每次執行方法後都需要返回一個實例,以確保能夠繼續調用其他方法。
chain方法會用傳入的obj創建一個_的實例,這個實例可以調用原型上的方法。從上面mixin的實現來看,每次調用原型方法後會將執行後的結果傳給result方法,在result內部會判斷你是否使用了鏈式調用(chain),如果是鏈式的,那麼就會將返回結果鏈式化(傳入chain中創建新的實例)。
鏈式調用一定要在結尾執行value方法,不然最後返回的是一個對象(最後一次創建的_實例)
數組函數
underscore構造方法上面並沒有直接對push、pop、shift等數組方法進行實現,但是鏈式調用的時候往往需要用到這些方法,所以在原型上對這些方法做了一些封裝,實現方法和mixin類似,這裏不再多做解釋。
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
var obj = this._wrapped;
method.apply(obj, arguments);
// 這句是好像是為了解決ie上的bug?
if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
return result(this, obj);
};
});
_.each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
return result(this, method.apply(this._wrapped, arguments));
};
});