1. 程式人生 > >underscore.js源碼研究(8)

underscore.js源碼研究(8)

又是 輸出 其中 article 對象儲存 參考 來看 rap 指針

概述

很早就想研究underscore源碼了,雖然underscore.js這個庫有些過時了,但是我還是想學習一下庫的架構,函數式編程以及常用方法的編寫這些方面的內容,又恰好沒什麽其它要研究的了,所以就了結研究underscore源碼這一心願吧。

underscore.js源碼研究(1)
underscore.js源碼研究(2)
underscore.js源碼研究(3)
underscore.js源碼研究(4)
underscore.js源碼研究(5)
underscore.js源碼研究(6)
underscore.js源碼研究(7)
underscore.js源碼研究(8)

參考資料:underscore.js官方註釋,undersercore 源碼分析,undersercore 源碼分析 segmentfault

鏈式調用

對於一個對象的方法,我們可以在方法裏面return this來使它支持鏈式調用。如果不修改原方法的源碼,我們可以通過下面的函數使它支持鏈式調用。

//其中obj是一個對象,functions是對象的方法名數組
function chained(obj, functions) {
    //由於functions是一個數組,所以可以使用foreach
    functions.forEach((funcName) => {
        const func = obj[funcName];
        //修改原方法
        obj[funcName] = function() {
            func.apply(this, arguments);
            return this;
        }
    })
}

示例如下:

let speaker = {
    haha() {
        console.log('haha');
    },
    yaya() {
        console.log('yaya');
    },
    lala() {
        console.log('lala');
    }
}

chained(speaker, ['haha', 'lala', 'yaya']);
speaker.haha().yaya().haha().lala().yaya();

輸出如下:

haha
yaya
haha
lala
yaya

underscore.js裏面的鏈式調用

underscore.js裏面的鏈式調用與上面的稍微有些不同。

它的機制是這樣的:

  1. 把underscore.js的方法全部掛載到_下面。
  2. 利用_.function方法遍歷所有掛載在_下面的方法,然後掛載到_.prototype下面,這樣生成的underscore對象就可以直接調用這些方法了。
  3. 在把方法掛載到_.prototype下面的時候,會利用類似上面的函數對方法進行改寫(增加return this)。
  4. 在改寫的時候建立一個標識_chain,來標識這個方法能不能鏈式調用。

下面來具體看看是怎麽運作的:

首先,利用下面的函數,可以得到一個數組,這個數組裏面全是掛載到_下面的方法名字

_function = _.methods = function(obj) {
    var names = [];
    for( var key in obj) {
        if(_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
};

然後我們對每一個方法名字,把它掛載到_.prototype下面

_.mixin = function(obj) {
    _.each(_.functions(obj), function(name) {
        var func = _[name] = obj[name];
        _.prototype[name] = function() {
            var args = [this._wrapped];
            push.apply(args, arguments);
            return chainResult(this, func.apply(_, args));
        };
    });
};
_.mixin(_);

從上面可以看到,在掛載的時候返回一個chainResult方法處理過的東西。而args就等於原對象+參數1+參數2+...組成的數組,所以func.apply(_, args)就是_[func](原對象,參數1,參數2,...)處理後的結果

再來看看chainResult是怎麽樣的:

var chainResult = function(instance, obj){
    return instance._chain ? _(obj).chain() : obj;
};

它判斷如果原對象能夠鏈式調用,那麽處理後的結果obj也能夠鏈式調用。怎麽讓結果也能鏈式調用呢?答案是使用_.chain方法:

_chain = function(obj) {
    var instance = _(obj);
    instance._chain = true;
    return instance;
}

這個方法和我們最開始的chained方法差不多,但是它會把原對象轉化為underscore對象,並且這個對象的**_chain屬性為真**,即它能夠被鏈式調用。

所以如果我們要在underscore.js中實現鏈式調用的話,直接用chain方法即可,實例如下:

_([1,2]).push(3).push(5) //輸出4,並不是我們想要的
_([1,2]).chain().push(3).push(5).value() //輸出[1, 2, 3, 5],正是我們想要的

可以看到,我們上面用到了value()函數,它能夠中斷鏈式調用,並不返回指針而是返回值,代碼如下:

_.prototype.value = function() {
    return this._wrapped;
}

等等,_wrapped又是什麽?它是生成underscore對象前的原對象,看下面的代碼:

var _ = function(obj) {
    if(obj instanceof _) return obj;
    if(!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
}

也就是說,在使用_生成underscore對象的時候,把原對象儲存在**_wrapped屬性**裏面了,所以_wrapped就是原對象。

underscore.js源碼研究(8)