underscore.js源碼的組織結構

最近開始讀源碼了,遵循大多數過來人的建議,underscore.js是最適合新手閱讀的一個js庫,因此我就火燒眉毛地下載了underscore.js,1.8.3版本。javascript

IIFE(當即執行函數)

與其餘第三方庫同樣,underscore.js也是經過IIFE來包裹本身的業務邏輯。java

(function(){
    ...
}.call(this))

經過傳入this(瀏覽器環境中其實就是window對象)來改變函數的做用域。(function(){}.call(this))等同於(function(){}()),就變成熟知的IIFE的寫法了。git

關於IIFEthis,附上兩篇文章,有助於更好地認識相關問題。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

面向對象編程(OOP)風格

在函數式編程(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的對象中,這樣,其餘對象也可以經過該mixin進行擴展。

clipboard.png

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的代碼結構起了很大幫助,很是感謝原做者。

相關文章
相關標籤/搜索