前言
終於,樓主的「Underscore 原始碼解讀系列」underscore-analysis 即將進入尾聲,關注下 timeline 會發現樓主最近加快了解讀速度。十一月,多事之秋,最近好多事情搞的樓主心力憔悴,身心俱疲,也想盡快把這個系列完結掉,也好了卻一件心事。
本文預計是解讀系列的倒數第二篇,最後一篇那麼顯然就是大總結了。樓主的 Underscore 系列解讀完整版地址 https://github.com/hanzichi/underscore-analysis
常規呼叫
之前寫的文章,關注點大多在具體的方法,具體的知識細節,也有讀者留言建議樓主講講整體架構,這是必須會講的,只是樓主把它安排在了最後,也就是本文,因為樓主覺得不掌握整體架構對於具體方法的理解也是沒有大的問題的。
Underscore 大多數時候的呼叫形式為 _.funcName(xx, xx)
,這也是 文件中 的呼叫方式。
1 |
_.each([1, 2, 3], alert); |
最簡單的實現方式,我們可以把 _
看做一個簡單的物件:
1 2 3 4 |
var _ = {}; _.each = function() { // ... }; |
在 JavaScript 中,一切皆物件,實際上,原始碼中的 _
變數是一個方法:
1 2 3 4 5 |
var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; |
為什麼會是方法?我們接下去看。
OOP
Underscore 支援 OOP 形式的呼叫:
1 |
_([1, 2, 3]).each(alert); |
這其實是非常經典的「無 new 構造」,_
其實就是一個 建構函式,_([1, 2, 3])
的結果就是一個物件例項,該例項有個 _wrapped
屬性,屬性值是 [1, 2, 3]
。例項要呼叫 each
方法,其本身沒有這個方法,那麼應該來自原型鏈,也就是說 _.prototype
上應該有這個方法,那麼,方法是如何掛載上去的呢?
方法掛載
現在我們已經明確以下兩點:
_
是一個函式(支援無 new 呼叫的建構函式)_
的屬性有很多方法,比如_.each
,_.template
等等
我們的目標是讓 _
的構造例項也能呼叫這些方法。仔細想想,其實也不難,我們可以遍歷 _
上的屬性,如果屬性值型別是函式,那麼就將函式掛到 _
的原型鏈上去。
原始碼中用來完成這件事的是 _.mixin
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// Add your own custom functions to the Underscore object. // 可向 underscore 函式庫擴充套件自己的方法 // obj 引數必須是一個物件(JavaScript 中一切皆物件) // 且自己的方法定義在 obj 的屬性上 // 如 obj.myFunc = function() {...} // 形如 {myFunc: function(){}} // 之後便可使用如下: _.myFunc(..) 或者 OOP _(..).myFunc(..) _.mixin = function(obj) { // 遍歷 obj 的 key,將方法掛載到 Underscore 上 // 其實是將方法淺拷貝到 _.prototype 上 _.each(_.functions(obj), function(name) { // 直接把方法掛載到 _[name] 上 // 呼叫類似 _.myFunc([1, 2, 3], ..) var func = _[name] = obj[name]; // 淺拷貝 // 將 name 方法掛載到 _ 物件的原型鏈上,使之能 OOP 呼叫 _.prototype[name] = function() { // 第一個引數 var args = [this._wrapped]; // arguments 為 name 方法需要的其他引數 push.apply(args, arguments); // 執行 func 方法 // 支援鏈式操作 return result(this, func.apply(_, args)); }; }); }; // Add all of the Underscore functions to the wrapper object. // 將前面定義的 underscore 方法新增給包裝過的物件 // 即新增到 _.prototype 中 // 使 underscore 支援物件導向形式的呼叫 _.mixin(_); |
_.mixin 方法可以向 Underscore 庫增加自己定義的方法:
1 2 3 4 5 6 7 |
_.mixin({ capitalize: function(string) { return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); } }); _("fabio").capitalize(); => "Fabio" |
同時,Underscore 也加入了一些 Array 原生的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Add all mutator Array functions to the wrapper. // 將 Array 原型鏈上有的方法都新增到 underscore 中 _.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); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; // 支援鏈式操作 return result(this, obj); }; }); // Add all accessor Array functions to the wrapper. // 新增 concat、join、slice 等陣列原生方法給 Underscore _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; }); |
鏈式呼叫
Underscore 也支援鏈式呼叫:
1 2 3 4 5 6 7 8 9 10 11 12 |
// 非 OOP 鏈式呼叫 _.chain([1, 2, 3]) .map(function(a) {return a * 2;}) .reverse() .value(); // [6, 4, 2] // OOP 鏈式呼叫 _([1, 2, 3]) .chain() .map(function(a){return a * 2;}) .first() .value(); // 2 |
乍一看似乎有 OOP 和非 OOP 兩種鏈式呼叫形式,其實只是一種,_.chain([1, 2, 3])
和 _([1, 2, 3]).chain()
的結果是一樣的。如何實現的?我們深入 chain
方法看下。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
_.chain = function(obj) { // 無論是否 OOP 呼叫,都會轉為 OOP 形式 // 並且給新的構造物件新增了一個 _chain 屬性 var instance = _(obj); // 標記是否使用鏈式操作 instance._chain = true; // 返回 OOP 物件 // 可以看到該 instance 物件除了多了個 _chain 屬性 // 其他的和直接 _(obj) 的結果一樣 return instance; }; |
我們看下 _.chain([1, 2, 3])
的結果,將引數代入函式中,其實就是對引數進行無 new 構造,然後返回例項,只是例項多了個 _chain
屬性,其他的和直接 _([1, 2, 3])
一模一樣。再來看 _([1, 2, 3]).chain()
,_([1, 2, 3])
返回構造例項,該例項有 chain
方法,呼叫方法,為例項新增 _chain
屬性,返回該例項物件。所以,這兩者效果是一致的,結果都是轉為了 OOP 的形式。
說了這麼多,似乎還沒講到正題上,它是如何「鏈」下去的?我們以如下程式碼為例:
1 2 3 4 5 |
_([1, 2, 3]) .chain() .map(function(a){return a * 2;}) .first() .value(); // 2 |
當呼叫 map
方法的時候,實際上可能會有返回值。我們看下 _.mixin
原始碼:
1 2 3 |
// 執行 func 方法 // 支援鏈式操作 return result(this, func.apply(_, args)); |
result
是一個重要的內部幫助函式(Helper function ):
1 2 3 4 5 6 7 |
// Helper function to continue chaining intermediate results. // 一個幫助方法(Helper function) var result = function(instance, obj) { // 如果需要鏈式操作,則對 obj 執行 chain 方法,使得可以繼續後續的鏈式操作 // 如果不需要,直接返回 obj return instance._chain ? _(obj).chain() : obj; }; |
如果需要鏈式操作(例項會有帶有 _chain 屬性),則對運算結果呼叫 chain
函式,使之可以繼續鏈式呼叫。
小結
Underscore 整體架構,或者說是基礎實現大概就是這個樣子,程式碼部分就講到這了,接下去系列解讀最後一篇,講講這段時間(幾乎也是歷時半年了)的一些心得體會吧,沒錢的就捧個人場吧!
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!