最近開始讀源碼了,遵循大多數過來人的建議,underscore.js是最適合新手閱讀的一個js庫,因此我就火燒眉毛地下載了underscore.js,1.8.3版本。javascript
與其餘第三方庫同樣,underscore.js也是經過IIFE
來包裹本身的業務邏輯。java
(function(){ ... }.call(this))
經過傳入this
(瀏覽器環境中其實就是window
對象)來改變函數的做用域。(function(){}.call(this))
等同於(function(){}())
,就變成熟知的IIFE的寫法了。git
關於IIFE
和this
,附上兩篇文章,有助於更好地認識相關問題。web
詳解javascript當即執行函數表達式(IIFE)
如何理解JavaScript中的函數調用和「this」編程
_
(function(){ var root = this; // 定義underscore對象 var _ = function (obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; // 把underscore對象暴露到全局做用域 if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } }.call(this));
能夠看到代碼中的_
被定義成了一個函數(暫時先無論函數裏面的代碼是什麼意思),函數也是對象,因此能夠在_
對象上添加一些方法。好比:segmentfault
(function(){ ... _.sayHello = function(name) { console.log('Hello, ' + name + '!'); }; }.call(this)); // 此時就能夠在咱們本身的js代碼中調用這個方法了 _.sayHello('underscore'); // 'Hello, underscore!'
這也是underscore.js大部分狀況下的調用方式,_.funcName(arg1, arg2)
,文檔中也是以這種方式調用的。設計模式
看,咱們好像也寫出了一個類underscore.js庫呢,好開森啊,哈哈~~~api
在函數式編程(FP)範式中,函數是拿來就用的,即使是對象,也只是函數的一個參數:瀏覽器
var arr = [1, 2, 3]; _.map(arr, function(item) { return item * 2; });
而在OOP中,函數通常是隸屬於某個對象的方法:架構
var arr = [1, 2, 3]; arr.map(function(item) { return item * 2; });
underscore.js是推崇函數式編程的,可是也提供了OOP風格的函數調用方式,僅須要經過_()
來包裹一下對象便可:
var arr = [1, 2, 3]; // FP風格 _.map(arr, function(item) { return itme * 2; }); // OOP風格 _(arr).map(function(item) { return item * 2; });
經過_()
構造出一個實例對象,下面來分析一下這個構造過程:
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; _([1, 2]); // 當執行這句代碼時,函數中的this指向window // obj instanceof _ === false, this instanceof _ === false // 因此執行 return new _(obj) // 在new的時候,this指向了構造函數_的實例,因此 this instanceof _ === true // this._wrapped = obj; 表示構造實例有一個屬性_wrapped,值爲obj(供後面_.mixin方法調用) // 當obj已是一個實例的時候,_(obj)直接返回這個實例: _(_([1,2])) == _([1,2]) // 自此,構造過程就結束了 // 這也就是所謂的 無new調用的構造函數
_
的構造實例怎麼調用_.func
這些方法呢?構造實例自己沒有這些方法,那麼這些方法應該存在原型鏈上,也就是_.prototype
上。_
又是如何把自身的方法掛載到_.prototype
上面呢?
思路:遍歷_
的屬性,若是某個屬性的類型是function
,就把該函數掛載到_.prototype
上:
_.mixin = function(obj) { // _.functions(obj) 返回obj中全部的方法 _.each(_.functions(obj), function(name) { _.prototype[name] = function() { var args = [this._wrapped]; // _(args1).func(args2) == _.func(args1, args2) // 右側func的參數比左側func的參數多一個,也就是this._wrapped push.apply(args, arguments); return obj[name].apply(_, args); // 方法調用結果 }; }); }; _.mixin(_); // 把 _ 對象上的方法都掛載到其原型上
想要實現鏈式調用,一般咱們會在支持鏈式調用的函數中返回對象自己:
var car = { run: function() { console.log('begin run'); return this; }, stop: function() { console.log('stopped'); } }; car.run().stop(); // => begin run // => stopped
underscore.js中鏈式調用的實現:
// 開啓一個鏈式調用 _.chain = function(obj) { var instance = _(obj); // 得到一個underscore實例 instance._chain = true; // 標識當前實例支持鏈式調用 return instance; }; // Helper函數,用來判斷 實例調用某一方法以後的返回結果是否支持繼續鏈式調用 var result = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; // 此時,咱們要把上面的_.mixin函數更新一下了 _.mixin = function(obj) { _.each(_.functions(obj), function(name) { _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result(this, obj[name].apply(_, args)); // this指向underscore實例 }; }); }; // 因爲鏈式調用的結果被轉化爲一個帶有_chain屬性的underscore實例對象, // {_wrapped: obj, _chain: true} // 因此想要獲取鏈式調用的結果時,須要有個取值過程 _.prototype.value = function() { return this._wrapped; // 返回被包裹的對象 }; // 鏈式調用demo var testArr = [1, 2, 3]; _.chain(testArr) .map(function(item) { return item * 5; }) .each(function(item) { console.log(item); }) .first() .value(); // 5 // _(testArr).chain().map().each()... 這種方式也能夠
mixin(混入)模式是增長代碼複用度的一個普遍使用的設計模式:向一個對象混入一系列方法,使之具有更強大的能力,這一系列方法又包裹在一個稱之爲mixin的對象中,這樣,其餘對象也可以經過該mixin進行擴展。
underscore.js也容許用戶擴充_
的功能,只須要在_.mixin函數上加上一行代碼:
// 完整版 _.mixin = function(obj) { _.each(_.functions(obj), function(name) { // 當obj是自定義對象時, // obj的方法被擴充到underscore的方法集合中以及_.prototype上 var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result(this, func.apply(_, args)); }; }); }; // demo _.mixin({ add5: function(num) { return num + 5; } }); _.add5(5); // 10 _([1,2,3]).chain().map(function(item) { return item * 10; }).first().add5().value(); // 15
理解了以上所說的內容,underscore.js的總體代碼架構就掌握的差很少了,接下去就能夠一個一個方法的去看具體代碼細節。纔剛讀了這些內容,我就感受收貨頗多,特別是讀到Mixin模式的時候,感受像發現了新大陸似的,接下來會去再看一些其餘類型的設計模式。
第一次讀源碼,認識不足或分析不到位的地方,還請路過的大大給以指正,謝謝!
如下兩個連接的內容對於我理清underscore.js的代碼結構起了很大幫助,很是感謝原做者。
underscore源碼分析 - 這是本gitbook,寫得特別好