Underscore整體架構淺析
前言
終于,樓主的「Underscore 源碼解讀系列」underscore-analysis 即將進入尾聲,關注下 timeline 會發(fā)現(xiàn)樓主最近加快了解讀速度。十一月,多事之秋,最近好多事情搞的樓主心力憔悴,身心俱疲,也想盡快把這個系列完結掉,也好了卻一件心事。
本文預計是解讀系列的倒數(shù)第二篇,***一篇那么顯然就是大總結了。樓主的 Underscore 系列解讀完整版地址https://github.com/hanzichi/u...
常規(guī)調用
之前寫的文章,關注點大多在具體的方法,具體的知識細節(jié),也有讀者留言建議樓主講講整體架構,這是必須會講的,只是樓主把它安排在了***,也就是本文,因為樓主覺得不掌握整體架構對于具體方法的理解也是沒有大的問題的。
Underscore 大多數(shù)時候的調用形式為 _.funcName(xx, xx),這也是 文檔中 的調用方式。
- _.each([1, 2, 3], alert);
最簡單的實現(xiàn)方式,我們可以把 _ 看做一個簡單的對象:
- var _ = {};
- _.each = function() {
- // ...
- };
在 JavaScript 中,一切皆對象,實際上,源碼中的 _ 變量是一個方法:
- var _ = function(obj) {
- if (obj instanceof _) return obj;
- if (!(this instanceof _)) return new _(obj);
- this._wrapped = obj;
- };
為什么會是方法?我們接下去看。
OOP
Underscore 支持 OOP 形式的調用:
- _([1, 2, 3]).each(alert);
這其實是非常經典的「無 new 構造」,_ 其實就是一個 構造函數(shù),_([1, 2, 3]) 的結果就是一個對象實例,該實例有個 _wrapped屬性,屬性值是 [1, 2, 3]。實例要調用 each 方法,其本身沒有這個方法,那么應該來自原型鏈,也就是說 _.prototype 上應該有這個方法,那么,方法是如何掛載上去的呢?
方法掛載
現(xiàn)在我們已經明確以下兩點:
- _ 是一個函數(shù)(支持無 new 調用的構造函數(shù))
- _ 的屬性有很多方法,比如 _.each,_.template 等等
我們的目標是讓 _ 的構造實例也能調用這些方法。仔細想想,其實也不難,我們可以遍歷 _ 上的屬性,如果屬性值類型是函數(shù),那么就將函數(shù)掛到 _ 的原型鏈上去。
源碼中用來完成這件事的是 _.mixin 方法:
- // Add your own custom functions to the Underscore object.
- // 可向 underscore 函數(shù)庫擴展自己的方法
- // obj 參數(shù)必須是一個對象(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() {
- // ***個參數(shù)
- var args = [this._wrapped];
- // arguments 為 name 方法需要的其他參數(shù)
- push.apply(args, arguments);
- // 執(zhí)行 func 方法
- // 支持鏈式操作
- return result(this, func.apply(_, args));
- };
- });
- };
- // Add all of the Underscore functions to the wrapper object.
- // 將前面定義的 underscore 方法添加給包裝過的對象
- // 即添加到 _.prototype 中
- // 使 underscore 支持面向對象形式的調用
- _.mixin(_);
_.mixin 方法可以向 Underscore 庫增加自己定義的方法:
- _.mixin({
- capitalize: function(string) {
- return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
- }
- });
- _("fabio").capitalize();
- => "Fabio"
同時,Underscore 也加入了一些 Array 原生的方法:
- // 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 等數(shù)組原生方法給 Underscore
- _.each(['concat', 'join', 'slice'], function(name) {
- var method = ArrayProto[name];
- _.prototype[name] = function() {
- return result(this, method.apply(this._wrapped, arguments));
- };
- });
鏈式調用
Underscore 也支持鏈式調用:
- // 非 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() 的結果是一樣的。如何實現(xiàn)的?我們深入 chain 方法看下。
_.chain = function(obj) {
- _.chain = function(obj) {
- // 無論是否 OOP 調用,都會轉為 OOP 形式
- // 并且給新的構造對象添加了一個 _chain 屬性
- var instance = _(obj);
- // 標記是否使用鏈式操作
- instance._chain = true;
- // 返回 OOP 對象
- // 可以看到該 instance 對象除了多了個 _chain 屬性
- // 其他的和直接 _(obj) 的結果一樣
- return instance;
- };
我們看下 _.chain([1, 2, 3]) 的結果,將參數(shù)代入函數(shù)中,其實就是對參數(shù)進行無 new 構造,然后返回實例,只是實例多了個_chain 屬性,其他的和直接 _([1, 2, 3]) 一模一樣。再來看 _([1, 2, 3]).chain(),_([1, 2, 3]) 返回構造實例,該實例有chain 方法,調用方法,為實例添加 _chain 屬性,返回該實例對象。所以,這兩者效果是一致的,結果都是轉為了 OOP 的形式。
說了這么多,似乎還沒講到正題上,它是如何「鏈」下去的?我們以如下代碼為例:
- _([1, 2, 3])
- .chain()
- .map(function(a){return a * 2;})
- .first()
- .value(); // 2
當調用 map 方法的時候,實際上可能會有返回值。我們看下 _.mixin 源碼:
- // 執(zhí)行 func 方法
- // 支持鏈式操作
- return result(this, func.apply(_, args));
result 是一個重要的內部幫助函數(shù)(Helper function ):
- // Helper function to continue chaining intermediate results.
- // 一個幫助方法(Helper function)
- var result = function(instance, obj) {
- // 如果需要鏈式操作,則對 obj 運行 chain 方法,使得可以繼續(xù)后續(xù)的鏈式操作
- // 如果不需要,直接返回 obj
- return instance._chain ? _(obj).chain() : obj;
- };
如果需要鏈式操作(實例會有帶有 _chain 屬性),則對運算結果調用 chain 函數(shù),使之可以繼續(xù)鏈式調用。
小結
Underscore 整體架構,或者說是基礎實現(xiàn)大概就是這個樣子,代碼部分就講到這了,接下去系列解讀***一篇,講講這段時間(幾乎也是歷時半年了)的一些心得體會吧,沒錢的就捧個人場吧!