Learn the overall architecture of underscore JS and build its own functional programming class library

Preface

The last article wrote about the overall architecture of jQuery. Learn the overall architecture of jQuery source code and build your own js class library

Although I've read a lot of underscorejs analysis articles, I always feel a little bit less. Maybe it's just a shallow idea on paper. You never know what you're going to do. So I decided to write an article about underscorejs.

The version of this article is v1.9.1.
unpkg.com source address: https://unpkg.com/underscore@...

Although many people haven't used underscore js, they should know how to use them when looking at official documents.

From a simple example of the official document chain:

_.chain([1, 2, 3]).reverse().value();
// => [3, 2, 1]

As you can see from the example, it supports chain invocation.

Readers can also follow the ideas of the article, open the download source code for debugging, which is more impressive.

call chaining

_ chain function source code:

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

This function is relatively simple, passing the obj call (). But the return value variable is the instance instance instance object. Add the attribute_chain assignment to true and return the intance object. But look at the example again, the instance object can call the reverse method and then the value method. Guessing supports OOP (object-oriented) calls.

With a problem, I looked at the code that defines the _function object.

_ Function object support OOP

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

If the parameter obj is already an instance of _then obj is returned.
If this is not an instance of, then manually new (obj);
When a new call is made again, the obj object is assigned to the property _wrapped.
That is to say, the final instance object is such a structure.
`{

_wrapped: 'parameter obj',

}`
Its prototype (obj). proto is prototype;

If you are not familiar with this piece of reader, you can see the following picture (previously written) Interviewer asked: JS inheritance Draw a picture.

Continue to analyze the official chain example. Take this example apart and write it in three steps.

var part1 = _.chain([1, 2, 3]);
var part2 = part1.reverse();
var part3 = part2.value();

// Without subsequent part1.reverse() operations
console.log(part1); // {__wrapped: [1, 2, 3], _chain: true}

console.log(part2); // {__wrapped: [3, 2, 1], _chain: true}

console.log(part3); // [3, 2, 1]

Think about it: reverse is the method on Array.prototype. Why support chain invocation?
Searching for reverse, you can see the following code:

And you can get an example by substituting it into this code (how can there be a visual sense of doing math problems in high school?)

_.chain([1,2,3]).reverse().value()
var ArrayProto = Array.prototype;
// These methods for traversing the array Array.prototype are assigned to. Prototype
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
    // The `method'here is a reverse function.
    var method = ArrayProto[name];
    _.prototype[name] = function() {
    // Here obj is an array [1, 2, 3]
    var obj = this._wrapped;
    // Arguments is a set of parameters, specifying that this of reverse points to obj and arguments, and executes the function. After execution, obj is [3, 2, 1]
    method.apply(obj, arguments);
    if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
    // The emphasis is on the chainResult function here.
    return chainResult(this, obj);
    };
});
// Helper function to continue chaining intermediate results.
var chainResult = function(instance, obj) {
    // If the _chain is true in an instance, the instance object {chain: true, this._wrapped: [3, 2, 1]} that supports chain invocation is returned, otherwise the object is returned directly [3, 2, 1].
    return instance._chain ? _(obj).chain() : obj;
};

if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
Refer to the above source code in this sentence, see this sentence is hard to understand. So I quickly searched for this sentence in github with double quotation marks. Represents all searches.

Two ISSUE s in the official library were searched, which probably means compatible with the lower version of IE. Interested can click to see.

I don't understand the meaning of this sentence.

[why delete obj[0]](https://github.com/jashkenas/...

Flow-based programming

At this point, even after the analysis of the chain call. chain() and function object. This stores data in instance objects {wrapped:', _chain: true}, and _chain determines whether chain calls are supported to be passed to the next function for processing. This approach is called stream-based programming.

Finally, after data processing, what should we do to return this data? underscore provides a value approach.

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

By the way, several aliases are provided. To JSON, value Of.
_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

The toString method is also provided.

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

String() here works the same as new String().
You can guess that the internal implementation is similar to the _function object.

var String = function(){
    if(!(this instanceOf String)) return new String(obj);
}
var chainResult = function(instance, obj) {
    return instance._chain ? _(obj).chain() : obj;
};

Careful readers will find out how (obj).chain() in the chainResult function implements chain calls.

And (obj) is the returned instance object {wrapped: obj}. How can there be a chain() method? There must be a place to mount this method on. prototype or other operations. This is. mixin().

_ Mixing mounts all static methods to. prototype, or you can mount custom methods

_ Mix in. However, it is too intrusive and often prone to problems such as coverage. Remember that React had mixin functionality and Vue had mixin functionality. However, mixins are generally not recommended or supported slowly after version iteration updates.

_.mixin = function(obj) {
    // Traversing all methods on objects
    _.each(_.functions(obj), function(name) {
        // For example, the chain, obj['chain'] function is assigned to [name] if it is customized, and func is the function. That is to say, there are not only function objects, but also prototype `
    var func = _[name] = obj[name];
    _.prototype[name] = function() {
        // Data objects processed
        var args = [this._wrapped];
        // Data Objects Processed Combined with arguments
        push.apply(args, arguments);
        // Chain call chain. apply (, args) parameter is added with the _chain attribute to support chain call.
        // _.chain = function(obj) {
        //    var instance = _(obj);
        //    instance._chain = true;
        //    return instance;
        };
        return chainResult(this, func.apply(_, args));
    };
    });
    // Finally, the _function object is returned.
    return _;
};

_.mixin(_);

_ Mixing () mounts the static method on. prototype, that is, the. prototype.chain method, that is, the. Chain method.

So. chain(obj) and (obj).chain() can achieve the same effect as chain call.

With regard to the chain call mentioned above, the author draws a picture, the so-called picture is worth thousands of words.

_ mixin mount custom method

Mount custom methods:
For instance:

_.mixin({
    log: function(){
        console.log('Gee, I was called.');
    }
})
_.log() // Gee, I was called.
_().log() // Gee, I was called.

_.functions(obj)

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

_ functions and. Methods traverse methods on objects, put them into an array, and sort them. Returns the sorted array.

How many methods and attributes are mounted by underscore JS in _and. prototype

Let's see how many static methods and attributes underscorejs mount on function objects, and how many methods and attributes mount on. prototype.

Use the for in loop to try it out. Look at the following code:

var staticMethods = [];
var staticProperty = [];
for(var name in _){
    if(typeof _[name] === 'function'){
        staticMethods.push(name);
    }
    else{
        staticProperty.push(name);
    }
}
console.log(staticProperty); // ["VERSION", "template Settings"]
console.log(staticMethods); // ["after", "all", "all Keys", "any", "assign",...] 138
var prototypeMethods = [];
var prototypeProperty = [];
for(var name in _.prototype){
    if(typeof _.prototype[name] === 'function'){
        prototypeMethods.push(name);
    }
    else{
        prototypeProperty.push(name);
    }
}
console.log(prototypeProperty); // []
console.log(prototypeMethods); // ["after", "all", "all Keys", "any", "assign",...] 152

According to these, the author draws a diagram of underscore JS prototype relationship, after all, a picture is worth thousands of words.

Overview of the Overall Architecture

Anonymous function self-execution

(function(){

}());

This ensures that the external environment is not polluted, while isolating the external environment, not the external impact on the internal environment.

External variables and functions can not be accessed by the outside world. External variables can be accessed by the inside, but the variables defined by the inside will not be accessed by the outside world.
Anonymous functions wrap code inside to prevent conflicts with other code and pollution of the global environment.
Readers who don't know much about self-executing functions can refer to this article.
[[Translated] JavaScript: Execute Functional Expressions (IIFE)]( https://segmentfault.com/a/11...

root processing

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

Support browser environment, node, Web Worker, node vm, Wechat applet.

export

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

I recommend this article for an explanation of the two pieces of code that root handles and exports. Tao Yu: How to Write Your Undercore in Undercore Series That's really good. The author will not dwell on it here.
In a word, underscorejs authors did not accomplish these treatments overnight, but gradually accumulated them and improved them after others mentioned ISSUE.

Support for amd modularization specification

if (typeof define == 'function' && define.amd) {
    define('underscore', [], function() {
        return _;
    });
}

_ noConflict Anti-Conflict Function

Source code:

// Temporarily on root, assign values back when noConflict is executed
var previousUnderscore = root._;
_.noConflict = function() {
    root._ = previousUnderscore;
    return this;
};

Use:

<script>
var _ = 'I'm me, different fireworks, don't cover me with anything else.';
</script>
<script src="https://unpkg.com/underscore@1.9.1/underscore.js">
</script>
<script>
var underscore = _.noConflict();
console.log(_); // 'I'm just me. Different fireworks. Don't cover me with anything else.
underscore.isArray([]) // true
</script>

summary

According to the example of chain call provided by official website,. chain([1, 2, 3]).reverse().value(); more in-depth debugging and tracking code, analysis of chain call (. chain() and (obj).chain(), OOP, flow-based programming, and. mixin() mounting method in. prototype, and finally the overall architecture analysis. Learning the overall architecture of Underscorejs is conducive to building its own functional programming class library.

The overall structure of the source code is analyzed in this paper.

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

    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 = '1.9.1';

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

    _.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);
        if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
        return chainResult(this, obj);
      };
    });

    _.each(['concat', 'join', 'slice'], function(name) {
      var method = ArrayProto[name];
      _.prototype[name] = function() {
        return chainResult(this, method.apply(this._wrapped, arguments));
      };
    });

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

    _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

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

    if (typeof define == 'function' && define.amd) {
      define('underscore', [], function() {
        return _;
      });
    }
}());

The next article may be about the overall source architecture of lodash.

Readers are welcome to comment on any inadequacies or improvements they may find. In addition, I think it's a good writing, which can be praised, commented and forwarded. It's also a kind of support for the author.

Recommended reading

underscorejs.org

undersercore-analysis

How to write your own underscore in underscore series

The author's previous articles

Learn the overall architecture of jQuery source code and build your own js class library

Interviewer asked: JS inheritance

Interviewer asked: JS points to this

Interviewer asked: Can you simulate the call and apply method of JS?

Interviewer asked: Can we simulate the bind method of JS implementation?

Interviewer asked: Can you simulate the new operator that implements JS?

The front end uses the puppeteer crawler to generate the PDF of React.js Books and merge them.

about

Author: Often in the name of Ruochuan mixed in rivers and lakes. On the front road | PPT enthusiasts | know little, only good at learning.

Personal Blog

Nuggets column Welcome your attention~

segmentfault Front-end Vision Column The front-end vision column has been opened, and you are welcome to pay attention to it.~

Knowing Front-end Vision Column The front-end vision column has been opened, and you are welcome to pay attention to it.~

github blog The relevant source code and resources are all put here. Find a star.^^~

Wechat Communication Group

Add Weixin lxchuan12 and indicate the source. Draw you in [Front-end Vision Communication Group]

Keywords: Javascript Programming JQuery github React

Added by luanne on Thu, 08 Aug 2019 14:13:11 +0300