The second article in the underscore series covers chain calls to underscore

preface

This article continues the underscore series: How to Write Your Own Underscore. Hope you have already read the previous article.

jQuery

We all know that jQuery can be chained, for example:

$("div").eq(0).css("width"."200px").show();Copy the code

Let’s write a simple demo to simulate a chain call:

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();Copy the code

The key to chained invocation in jQuery is to return this to the called object. To simplify the demo:

var jQuery = {
    eq: function(){
        console.log('Call eq method');
        return this;
    },
    show: function(){
        console.log('Call the show method');
        return this;
    }
}

jQuery.eq().show();Copy the code

_.chain

Underscore does not use chain calls by default, but if you want to use chain calls, you can do so using the _. Chain function:

_.chain([1.2.3.4])
.filter(function(num) { return num % 2= =0; })
.map(function(num) { return num * num })
.value(); / / [4, 16]Copy the code

Let’s see what the _. Chain method does:

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

For example, _. Chain ([1, 2, 3]) returns an object:

{
    _chain: true._wrapped: [1.2.3]}Copy the code

The object has various methods over its prototype that we can call directly.

The problem is that the prototype methods don’t return this, as jQuery does, so if you call a method once, you can’t follow up with other methods…

But if we pass the return value of the function as an argument to the _. Chain function, we can then call other methods.

Write a compact 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);
// Pass the return value of the last function to _.chain, and continue calling other functions
var res2 = _.chain(res).shift();

console.log(res2); / / [2, 3, 4]Copy the code

However, this is too complicated. Can’t the chain process be automated? If I were a developer, I’d want to write:

_.chain([1.2.3]).push(4).shift()Copy the code

So let’s optimize the implementation again:

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);Copy the code

In each function, we wrap the return value of the function with chainResult to generate an object of the following form:

{
    _wrapped: some value, 
    _chain: true
}Copy the code

The prototype of this object has functions whose return values are passed as arguments to chainResult, which in turn returns an object whose return value is stored in _wrapped, thus implementing the chain call.

That’s how chain calls work, but in this case, we need to change every function…

Fortunately, underscore all functions are mounted into _ function objects and functions on _. Prototype copy all functions from _ function objects into _. Prototype via _. Mixin functions.

So to implement chain calls, we also need to make some changes to the _. Mixin method in our previous article, Underscore Series: How to Write Your Own Underscores:

/ / modify before
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(_);Copy the code
/ / modified
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(_);Copy the code

_.value

According to the analysis process above, we know that if we print:

console.log(_.chain([1.2.3]).push(4).shift());Copy the code

{_chain: true, _wrapped: [2, 3, 4]}

But I want the value to be [2, 3, 4].

So, we also need to provide a value method that, when executed, returns the value of the current _wrapped.

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

At this point, the call method is:

var arr = _.chain([1.2.3]).push(4).shift().value();
console.log(arr) / / [2, 3, 4]Copy the code

The final code

In conjunction with the previous article, the final underscore code is organized as follows:

(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 (typeofexports ! ='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();
    };

    /** * add your own method */ before _. 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; }; }) ()Copy the code

The underscore series

Underscore Catalogue Address: github.com/mqyqingfeng… .

The Underscore series is designed to help you read the source code and write your own undercore underscores, highlighting code architectures, chain calls, internal functions, and template engines.

If there is any mistake or not precise place, please be sure to give correction, thank you very much. If you like or are inspired by it, welcome star and encourage the author.