underscore源碼分析之基礎方法

underscore源碼分析之基礎方法


本文是underscore源碼剖析系列的第二篇,主要介紹underscore中一些基礎方法的實現。segmentfault

mixin

在上篇文章underscore總體架構分析中,咱們講過_上面的方法有兩種掛載方式,一個是掛載到_構造函數上以_.map(arr)的形式直接調用(在後文上統稱構造函數調用),另外一種則是掛到_.prototype上以_(arr).map()的形式被實例調用(在後文上統稱原型調用)數組

翻一遍underscore源碼你會發現underscore中的方法都是直接掛到_構造函數上實現的,可是會經過mixin方法來將_上面的方法擴展到_.prototype上面,這樣這些方法既能夠直接調用,又能夠經過實例來調用。架構

_.mixin = function(obj) {
    // 遍歷obj上全部的方法
    _.each(_.functions(obj), function(name) {
        // 保存方法的引用
        var func = _[name] = obj[name];
        _.prototype[name] = function() {
            // 將一開始傳入的值放到數組中
            var args = [this._wrapped];
            // 將方法的參數一塊兒push到數組中(這裏處理的很好,保證了func方法參數的順序)
            push.apply(args, arguments);
            // 這裏先用apply方法執行了func,並將結果傳給了result
            return result(this, func.apply(_, args));
        };
    });
};

_.mixin(_);

從這段代碼中咱們能夠看出,mixin方法將_上的全部方法經過遍歷的形式掛載到了_.prototype上面。app

細心觀察一下,構造函數調用和原型調用的區別在哪裏?
沒錯,區別就在於調用方式和傳參,構造函數調用時通常會把要處理的值當作第一個參數傳入,而原型調用的時候會把要處理的值傳入_構造函數來建立一個實例。函數

var arr = [1, 2, 3]
var func = function(item) {
    console.log(item);
}
// 構造函數調用時arr被傳入第一個參數
_.each(arr, func)
// 原型調用的時候,arr被當作參數傳給_方法來建立一個實例
_(arr).each(func)
// 鏈式調用,和上面相似
_.chain(arr).each(func)

從上一節中咱們知道,在建立一個_的實例時,會用this._wrapped將傳入的值保存起來,因此在mixin裏面這一句:var args = [this._wrapped];是將咱們傳給_的值放到args數組第一項中,以後再將arguments也放入args數組中,藉助apply方法執行當前遍歷的方法(在這個例子中是each),這個時候傳給each方法的是arr和func,正好和原來直接_.each調用each傳入參數的順序是同樣的(underscore中的方法第一項基本上都是要處理的數據)。源碼分析

鏈式調用

那麼上面最後return result(this, func.apply(_, args)),result又是作什麼的呢?this

首先來看result源碼:prototype

var result = function(instance, obj) {
    // 首先判斷是否使用鏈式調用,若是是,那就繼續將剛剛執行後返回的結果鏈式調用一下,若是不是,則直接返回執行後的結果
    return instance._chain ? _(obj).chain() : obj;
};
_.chain = function(obj) {
    // 建立一個實例
    var instance = _(obj);
    // 給這個實例加個_chain屬性來代表這是鏈式調用
    instance._chain = true;
    return instance;
};

咱們知道underscore中也是有和jQuery相似的鏈式調用,來看一下鏈式調用的例子:code

var arr = [1, 2, 3]
var newArr = _.chain(a).map(function(item) {
    return item + 1
}).filter(function(item) {
    return item > 2
}).value()

鏈式調用的關鍵在於每次執行方法後都須要返回一個實例,以確保可以繼續調用其餘方法。
chain方法會用傳入的obj建立一個_的實例,這個實例能夠調用原型上的方法。從上面mixin的實現來看,每次調用原型方法後會將執行後的結果傳給result方法,在result內部會判斷你是否使用了鏈式調用(chain),若是是鏈式的,那麼就會將返回結果鏈式化(傳入chain中建立新的實例)。
鏈式調用必定要在結尾執行value方法,否則最後返回的是一個對象(最後一次建立的_實例)對象

數組函數

underscore構造方法上面並無直接對push、pop、shift等數組方法進行實現,可是鏈式調用的時候每每須要用到這些方法,因此在原型上對這些方法作了一些封裝,實現方法和mixin相似,這裏再也不多作解釋。

_.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);
    // 這句是好像是爲了解決ie上的bug?
    if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
        return result(this, obj);
    };
});

_.each(['concat', 'join', 'slice'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
        return result(this, method.apply(this._wrapped, arguments));
    };
});
相關文章
相關標籤/搜索