Underscore源碼解析(一)

      最近準備折騰一下backbone.js,在事先了解了backbone以後,我知道了backbone對underscore這個庫有着強依賴,正好underscore以前也沒使用過,因而我就想先把underscore完全瞭解一下,這樣以後折騰backbone的時候也少一點阻礙。javascript

    underscore是一個很實用且小巧的框架,提供了不少咱們在編程時須要的基本功能函數,並且他沒有擴展javascript的原生對象,主要涉及對Object、Array、Function的操做。
    我曾經問個人朋友@小鬍子哥 怎麼學習一個框架?他給個人回答是:「直接看源碼。」如今想一想深感贊成,由於研究源碼是最直接的學習途徑,能夠深刻地瞭解這個框架的思想和精髓,同時也能學習框架做者的編程技巧,提高本身的coding水平。
    好了,題外話就說到這裏,下面我們進入正題。

簡化源碼看結構

    我此次看的underscore版本是1.3.3,整個文件也就1000多行,我把代碼簡化了一下,並加入了相關的註釋:java

  1 // underscore的代碼包裹在一個匿名自執行函數中
  2 (function() {
  3 
  4     // 建立一個全局對象, 在瀏覽器中表示爲window對象, 在Node.js中表示global對象
  5     var root = this;
  6 
  7     // 保存"_"(下劃線變量)被覆蓋以前的值
  8     // 若是出現命名衝突或考慮到規範, 可經過_.noConflict()方法恢復"_"被Underscore佔用以前的值, 並返回Underscore對象以便從新命名
  9     var previousUnderscore = root._;
 10 
 11     // 建立一個空的對象常量, 便於內部共享使用
 12     var breaker = {};
 13 
 14     // 將內置對象的原型鏈緩存在局部變量
 15     var ArrayProto = Array.prototype, 
 16     ObjProto = Object.prototype, 
 17     FuncProto = Function.prototype;
 18 
 19     // 將內置對象原型中的經常使用方法緩存在局部變量
 20     var slice = ArrayProto.slice, 
 21     unshift = ArrayProto.unshift, 
 22     toString = ObjProto.toString,
 23     hasOwnProperty = ObjProto.hasOwnProperty;
 24 
 25     // 這裏定義了一些JavaScript 1.6提供的新方法
 26     // 若是宿主環境中支持這些方法則優先調用, 若是宿主環境中沒有提供, 則會由Underscore實現
 27     var nativeForEach = ArrayProto.forEach, 
 28     nativeMap = ArrayProto.map, 
 29     nativeReduce = ArrayProto.reduce, 
 30     nativeReduceRight = ArrayProto.reduceRight, 
 31     nativeFilter = ArrayProto.filter, 
 32     nativeEvery = ArrayProto.every, 
 33     nativeSome = ArrayProto.some, 
 34     nativeIndexOf = ArrayProto.indexOf, 
 35     nativeLastIndexOf = ArrayProto.lastIndexOf, 
 36     nativeIsArray = Array.isArray, 
 37     nativeKeys = Object.keys, 
 38     nativeBind = FuncProto.bind;
 39 
 40     // 建立對象式的調用方式, 將返回一個Underscore包裝器, 包裝器對象的原型中包含Underscore全部方法(相似與將DOM對象包裝爲一個jQuery對象)
 41     var _ = function(obj) {
 42         // 全部Underscore對象在內部均經過wrapper對象進行構造
 43         return new wrapper(obj);
 44     };
 45 
 46     // 針對不一樣的宿主環境, 將Undersocre的命名變量存放到不一樣的對象中
 47     if( typeof exports !== 'undefined') {// Node.js環境
 48         if( typeof module !== 'undefined' && module.exports) {
 49             exports = module.exports = _;
 50         }
 51         exports._ = _;
 52     } else {// 瀏覽器環境中Underscore的命名變量被掛在window對象中
 53         root['_'] = _;
 54     }
 55 
 56     // 版本聲明
 57     _.VERSION = '1.3.3';
 58 
 59     //在_對象上定義各類方法
 60     . . . . . .
 61 
 62     // underscore對象的包裝函數
 63     var wrapper = function(obj) {
 64         // 原始數據存放在包裝對象的_wrapped屬性中
 65         this._wrapped = obj;
 66     };
 67 
 68     // 將Underscore的原型對象指向wrapper的原型, 所以經過像wrapper原型中添加方法, Underscore對象也會具有一樣的方法
 69     _.prototype = wrapper.prototype;
 70 
 71     // 返回一個對象, 若是當前Underscore調用了chain()方法(即_chain屬性爲true), 則返回一個被包裝的Underscore對象, 不然返回對象自己
 72     // result函數用於在構造方法鏈時返回Underscore的包裝對象
 73     var result = function(obj, chain) {
 74         return chain ? _(obj).chain() : obj;
 75     };
 76 
 77     // 將一個自定義方法添加到Underscore對象中(實際是添加到wrapper的原型中, 而Underscore對象的原型指向了wrapper的原型)
 78     var addToWrapper = function(name, func) {
 79         // 向wrapper原型中添加一個name函數, 該函數調用func函數, 並支持了方法鏈的處理
 80         wrapper.prototype[name] = function() {
 81             // 獲取func函數的參數, 並將當前的原始數據添加到第一個參數
 82             var args = slice.call(arguments);
 83             unshift.call(args, this._wrapped);
 84             // 執行函數並返回結果, 並經過result函數對方法鏈進行封裝, 若是當前調用了chain()方法, 則返回封裝後的Underscore對象, 不然返回對象自己
 85             return result(func.apply(_, args), this._chain);
 86         };
 87     };
 88 
 89     // 將內部定義的_(即Underscore方法集合對象)中的方法複製到wrapper的原型鏈中(即Underscore的原型鏈中)
 90     // 這是爲了在構造對象式調用的Underscore對象時, 這些對象也會具備內部定義的Underscore方法
 91     _.mixin(_);
 92 
 93     // 將Array.prototype中的相關方法添加到Underscore對象中, 所以在封裝後的Underscore對象中也能夠直接調用Array.prototype中的方法
 94     // 如: _([]).push()
 95     each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
 96         // 獲取Array.prototype中對應方法的引用
 97         var method = ArrayProto[name];
 98         // 將該方法添加到Underscore對象中(實際是添加到wrapper的原型對象, 所以在建立Underscore對象時同時具有了該方法)
 99         wrapper.prototype[name] = function() {
100             // _wrapped變量中存儲Underscore對象的原始值
101             var wrapped = this._wrapped;
102             // 調用Array對應的方法並返回結果
103             method.apply(wrapped, arguments);
104             var length = wrapped.length;
105             if((name == 'shift' || name == 'splice') && length === 0)
106                 delete wrapped[0];
107             // 即便是對於Array中的方法, Underscore一樣支持方法鏈操做
108             return result(wrapped, this._chain);
109         };
110     });
111 
112     // 做用同於上一段代碼, 將數組中的一些方法添加到Underscore對象, 並支持了方法鏈操做
113     // 區別在於上一段代碼所添加的函數, 均返回Array對象自己(也多是封裝後的Array), concat, join, slice方法將返回一個新的Array對象(也多是封裝後的Array)
114     each(['concat', 'join', 'slice'], function(name) {
115         var method = ArrayProto[name];
116         wrapper.prototype[name] = function() {
117             return result(method.apply(this._wrapped, arguments), this._chain);
118         };
119     });
120 
121     // 對Underscore對象進行鏈式操做的聲明方法
122     wrapper.prototype.chain = function() {
123         // this._chain用來標示當前對象是否使用鏈式操做
124         // 對於支持方法鏈操做的數據, 通常在具體方法中會返回一個Underscore對象, 並將原始值存放在_wrapped屬性中, 也能夠經過value()方法獲取原始值
125         this._chain = true;
126         return this;
127     };
128 
129     // 返回被封裝的Underscore對象的原始值(存放在_wrapped屬性中)
130     wrapper.prototype.value = function() {
131         return this._wrapped;
132     };
133 
134 }).call(this);

小結

    underscore這個庫的結構(或者說思路)大體是這樣的:編程

        建立一個包裝器, 將一些原始數據進行包裝,全部的undersocre對象, 內部均經過wrapper函數進行構造和封裝數組

        underscore與wrapper的內部關係是:瀏覽器

            - 內部定義變量_, 將underscore相關的方法添加到_, 這樣就能夠支持函數式的調用, 如_.bind()緩存

            - 內部定義wrapper類, 將_的原型對象指向wrapper類的原型app

            - 將underscore相關的方法添加到wrapper原型, 建立的_對象就具有了underscore的方法框架

            - 將Array.prototype相關方法添加到wrapper原型, 建立的_對象就具有了Array.prototype中的方法函數

            - new _()時實際建立並返回了一個wrapper()對象, 並將原始數組存儲到_wrapped變量, 並將原始值做爲第一個參數調用對應方法學習

 

    以後我會對underscore中全部方法的具體實現進行介紹,感謝關注 

相關文章
相關標籤/搜索