underscore 系列之鏈式調用

underscore 系列第二篇,講解 underscore 的鏈式調用css

前言

本文接着上篇《underscore 系列之如何寫本身的 underscore》,閱讀本篇前,但願你已經閱讀了上一篇。node

jQuery

咱們都知道 jQuery 能夠鏈式調用,好比:git

$("div").eq(0).css("width", "200px").show();複製代碼

咱們寫個簡單的 demo 模擬鏈式調用:github

function JQuery(selector) {
    this.elements = [];
    var nodeLists = document.getElementsByTagName(selector);
    for (var i = 0; i < nodeLists.length; i++) {
        this.elements.push(nodeLists[i]);
    }
    return this;
}

JQuery.prototype = {
    eq: function(num){
        this.elements = [this.elements[0]];
        return this;
    },
    css: function(prop, val) {
        this.elements.forEach(function(el){
            el.style[prop] = val;
        })
        return this;
    },
    show: function() {
        this.css('display', 'block');
        return this;
    }

}

window.$ = function(selector){
    return new JQuery(selector)
}

$("div").eq(0).css("width", "200px").show();複製代碼

jQuery 之因此能實現鏈式調用,關鍵就在於經過 return this,返回調用對象。再精簡下 demo 就是:架構

var jQuery = {
    eq: function(){
        console.log('調用 eq 方法');
        return this;
    },
    show: function(){
        console.log('調用 show 方法');
        return this;
    }
}

jQuery.eq().show();複製代碼

_.chain

在 underscore 中,默認不使用鏈式調用,可是若是你想使用鏈式調用,你能夠經過 _.chain 函數實現:app

_.chain([1, 2, 3, 4])
.filter(function(num) { return num % 2 == 0; })
.map(function(num) { return num * num })
.value(); // [4, 16]複製代碼

咱們看看 _.chain 這個方法都作了什麼:函數

_.chain = function (obj) {
    var instance = _(obj);
    instance._chain = true;
    return instance;
};複製代碼

咱們以 [1, 2, 3] 爲例,_.chain([1, 2, 3]) 會返回一個對象:優化

{
    _chain: true,
    _wrapped: [1, 2, 3]
}複製代碼

該對象的原型上有着 underscore 的各類方法,咱們能夠直接調用這些方法。ui

可是問題在於原型上的這些方法並無像 jQuery 同樣,返回 this ,因此若是你調用了一次方法,就沒法接着調用其餘方法了……this

可是試想下,咱們將函數的返回值做爲參數再傳入 _.chain 函數中,不就能夠接着調用其餘方法了?

寫一個精簡的 Demo:

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

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

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

_.prototype.shift = function(num) {
    this._wrapped.shift()
    return this._wrapped
}

var res = _.chain([1, 2, 3]).push(4);
// 將上一個函數的返回值,傳入 _.chain,而後再繼續調用其餘函數
var res2 = _.chain(res).shift();

console.log(res2); // [2, 3, 4]複製代碼

然而這也太複雜了吧,難道 chain 這個過程不能是自動化的嗎?若是我是開發者,我確定但願直接寫成:

_.chain([1, 2, 3]).push(4).shift()複製代碼

因此咱們再優化一下實現方式:

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

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

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

_.prototype.push = function(num) {
    this._wrapped.push(num);
    return chainResult(this, this._wrapped)
}

_.prototype.shift = function() {
    this._wrapped.shift();
    return chainResult(this, this._wrapped)
}

var res = _.chain([1, 2, 3]).push(4).shift();

console.log(res._wrapped);複製代碼

咱們在每一個函數中,都用 chainResult 將函數的返回值包裹一遍,再生成一個相似如下這種形式的對象:

{
    _wrapped: some value, 
    _chain: true
}複製代碼

該對象的原型上有各類函數,而這些函數的返回值做爲參數傳入了 chainResult,該函數又會返回這樣一個對象,函數的返回值就保存在 _wrapped 中,這樣就實現了鏈式調用。

_.chain鏈式調用原理就是這樣,但是這樣的話,咱們須要對每一個函數都進行修改呀……

幸運的是,在 underscore 中,全部的函數是掛載到 _ 函數對象中的,_.prototype 上的函數是經過 _.mixin 函數將 _ 函數對象中的全部函數複製到 _.prototype 中的。

因此爲了實現鏈式調用,咱們還須要對上一篇《underscore 系列之如何寫本身的 underscore》 中的 _.mixin 方法進行必定修改:

// 修改前
var ArrayProto = Array.prototype;
var push = ArrayProto.push;

_.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 func.apply(_, args);
        };
    });
    return _;
};

_.mixin(_);複製代碼
// 修改後
var ArrayProto = Array.prototype;
var push = ArrayProto.push;

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

_.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));
        };
    });
    return _;
};

_.mixin(_);複製代碼

_.value

根據上面的分析過程,咱們知道若是咱們打印:

console.log(_.chain([1, 2, 3]).push(4).shift());複製代碼

其實會打印一個對象 {_chain: true, _wrapped: [2, 3, 4] }

但是我但願得到值是 [2, 3, 4] 呀!

因此,咱們還須要提供一個 value 方法,當執行 value 方法的時候,就返回當前 _wrapped 的值。

_.prototype.value = function() {
    return this._wrapped;
};複製代碼

此時調用方式爲:

var arr = _.chain([1, 2, 3]).push(4).shift().value();
console.log(arr) // [2, 3, 4]複製代碼

最終代碼

結合上一篇文章,最終的 underscore 代碼組織結構以下:

(function() {

    var root = (typeof self == 'object' && self.self == self && self) ||
        (typeof global == 'object' && global.global == global && global) ||
        this || {};

    var ArrayProto = Array.prototype;

    var push = ArrayProto.push;

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

    if (typeof exports != 'undefined' && !exports.nodeType) {
        if (typeof module != 'undefined' && !module.nodeType && module.exports) {
            exports = module.exports = _;
        }
        exports._ = _;
    } else {
        root._ = _;
    }

    _.VERSION = '0.2';

    var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

    var isArrayLike = function(collection) {
        var length = collection.length;
        return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
    };

    _.each = function(obj, callback) {
        var length, i = 0;

        if (isArrayLike(obj)) {
            length = obj.length;
            for (; i < length; i++) {
                if (callback.call(obj[i], obj[i], i) === false) {
                    break;
                }
            }
        } else {
            for (i in obj) {
                if (callback.call(obj[i], obj[i], i) === false) {
                    break;
                }
            }
        }

        return obj;
    }

    _.isFunction = function(obj) {
        return typeof obj == 'function' || false;
    };

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

    /** * 在 _.mixin(_) 前添加本身定義的方法 */
    _.reverse = function(string){
        return string.split('').reverse().join('');
    }

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

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

    _.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));
            };
        });
        return _;
    };

    _.mixin(_);

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

})()複製代碼

underscore 系列

underscore 系列目錄地址:github.com/mqyqingfeng…

underscore 系列預計寫八篇左右,重點介紹 underscore 中的代碼架構、鏈式調用、內部函數、模板引擎等內容,旨在幫助你們閱讀源碼,以及寫出本身的 undercore。

若是有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。若是喜歡或者有所啓發,歡迎star,對做者也是一種鼓勵。

相關文章
相關標籤/搜索