underscore.js源碼研究(8)
概述
很早就想研究underscore源碼了,雖然underscore.js這個庫有些過時了,但是我還是想學習一下庫的架構,函數式編程以及常用方法的編寫這些方面的內容,又恰好沒什麽其它要研究的了,所以就了結研究underscore源碼這一心願吧。
underscore.js源碼研究(1)
underscore.js源碼研究(2)
underscore.js源碼研究(3)
underscore.js源碼研究(4)
underscore.js源碼研究(5)
underscore.js源碼研究(6)
underscore.js源碼研究(7)
underscore.js源碼研究(8)
參考資料:underscore.js官方註釋,undersercore 源碼分析,undersercore 源碼分析 segmentfault
鏈式調用
對於一個對象的方法,我們可以在方法裏面return this來使它支持鏈式調用。如果不修改原方法的源碼,我們可以通過下面的函數使它支持鏈式調用。
//其中obj是一個對象,functions是對象的方法名數組 function chained(obj, functions) { //由於functions是一個數組,所以可以使用foreach functions.forEach((funcName) => { const func = obj[funcName]; //修改原方法 obj[funcName] = function() { func.apply(this, arguments); return this; } }) }
示例如下:
let speaker = { haha() { console.log('haha'); }, yaya() { console.log('yaya'); }, lala() { console.log('lala'); } } chained(speaker, ['haha', 'lala', 'yaya']); speaker.haha().yaya().haha().lala().yaya();
輸出如下:
haha
yaya
haha
lala
yaya
underscore.js裏面的鏈式調用
underscore.js裏面的鏈式調用與上面的稍微有些不同。
它的機制是這樣的:
- 把underscore.js的方法全部掛載到_下面。
- 利用_.function方法遍歷所有掛載在_下面的方法,然後掛載到_.prototype下面,這樣生成的underscore對象就可以直接調用這些方法了。
- 在把方法掛載到_.prototype下面的時候,會利用類似上面的函數對方法進行改寫(增加return this)。
- 在改寫的時候建立一個標識_chain,來標識這個方法能不能鏈式調用。
下面來具體看看是怎麽運作的:
首先,利用下面的函數,可以得到一個數組,這個數組裏面全是掛載到_下面的方法名字。
_function = _.methods = function(obj) {
var names = [];
for( var key in obj) {
if(_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
然後我們對每一個方法名字,把它掛載到_.prototype下面:
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return chainResult(this, func.apply(_, args));
};
});
};
_.mixin(_);
從上面可以看到,在掛載的時候返回一個chainResult方法處理過的東西。而args就等於原對象+參數1+參數2+...組成的數組,所以func.apply(_, args)
就是_[func](原對象,參數1,參數2,...)
處理後的結果。
再來看看chainResult是怎麽樣的:
var chainResult = function(instance, obj){
return instance._chain ? _(obj).chain() : obj;
};
它判斷如果原對象能夠鏈式調用,那麽處理後的結果obj也能夠鏈式調用。怎麽讓結果也能鏈式調用呢?答案是使用_.chain方法:
_chain = function(obj) {
var instance = _(obj);
instance._chain = true;
return instance;
}
這個方法和我們最開始的chained方法差不多,但是它會把原對象轉化為underscore對象,並且這個對象的**_chain屬性為真**,即它能夠被鏈式調用。
所以如果我們要在underscore.js中實現鏈式調用的話,直接用chain方法即可,實例如下:
_([1,2]).push(3).push(5) //輸出4,並不是我們想要的
_([1,2]).chain().push(3).push(5).value() //輸出[1, 2, 3, 5],正是我們想要的
可以看到,我們上面用到了value()函數,它能夠中斷鏈式調用,並不返回指針而是返回值,代碼如下:
_.prototype.value = function() {
return this._wrapped;
}
等等,_wrapped又是什麽?它是生成underscore對象前的原對象,看下面的代碼:
var _ = function(obj) {
if(obj instanceof _) return obj;
if(!(this instanceof _)) return new _(obj);
this._wrapped = obj;
}
也就是說,在使用_生成underscore對象的時候,把原對象儲存在**_wrapped屬性**裏面了,所以_wrapped就是原對象。
underscore.js源碼研究(8)