Research on source code of underscore.js (8)


I have wanted to study the underscore source code for a long time. Although the underscore.js library is a bit outdated, I still want to learn about the library's architecture, functional programming and the writing of common methods, but there is nothing else to study. , so let's end the wish to study the underscore source code.

Underscore.js source code research (1)
underscore.js source code research (2)

References: official notes of underscore.js , undersercore source code analysis , undersercore source code analysis segmentfault

chain call

For a method of an object, we can return this in the method to make it support chaining. If we do not modify the source code of the original method, we can make it support chained calls through the following function.

function chained(obj, functions) {
    functions.forEach((funcName) => {
        const func = obj[funcName];
        obj[funcName] = function() {
            func.apply(this, arguments);
            return this;

An example is as follows:

let speaker = {
    haha() {
    yaya() {
    lala() {

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

The output is as follows:


Chained calls in underscore.js

The chaining in underscore.js is slightly different from the above.

Its mechanism is as follows:

  1. Mount all the methods of underscore.js under _.
  2. Use the _.function method to traverse all the methods mounted under _, and then mount them under _.prototype, so that the generated underscore object can directly call these methods.
  3. When a method is mounted under _.prototype, a function similar to the above will be used to rewrite the method (add return this).
  4. When rewriting, an identifier _chain is established to identify whether the method can be called in a chain, and it is convenient for those methods that cannot be called in a chain to rewrite the identifier.

Here's a look at how it works:

First, using the following function, you can get an array, which is full of method names mounted under _.

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

Then we mount it under _.prototype for each method name:

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

As you can see from the above, it returns something processed by the chainResult method when it is mounted. And args is equal to the array composed of the original object + parameter 1 + parameter 2 +..., so it func.apply(_, args)is the _[func](原对象,参数1,参数2,...)processed result .

Let's see how chainResult looks like:

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

It judges that if the original object can be chained, then the processed result obj can also be chained. How can the result be chained? The answer is to use the _.chain method:

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

This method is similar to our original chained method, but it will convert the original object into an underscore object, and the _chain property of this object is true, that is, it can be chained.

So if we want to implement chain calls in underscore.js, we can directly use the chain method. The example is as follows:

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

As you can see, we used the value() function above, which can interrupt the chain call and return a value instead of a pointer. The code is as follows:

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

Wait, what is _wrapped? It is the original object before generating the underscore object, see the following code:

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

That is to say, when using _ to generate an underscore object, the original object is stored in the _wrapped attribute, so _wrapped is the original object.

Guess you like