The first article in the underscore series explains how code is organized for underscore

preface

In the JavaScript series, we wrote a lot of functions, such as stabilization, throttling, weight, type of judgment and flat arrays, copy depth, search array elements, general traversal, currie, function composition, function, memory, out-of-order and so on, can we how to organize these functions, form their own a tool function library? At this point, we need to use underscore for a lesson.

Their implementation

If we were to organize these functions ourselves, what would we do? Here’s what I think I’ll do:

(function(){
    var root = this;

    var _ = {};

    root._ = _;

    // Add your own methods here
    _.reverse = function(string){
        return string.split(' ').reverse().join(' ');
    }

})()

_.reverse('hello'); = >'olleh'Copy the code

We add all the methods to an object named _, and then mount that object onto the global object.

The reason for not going directly to Window. _ = _ is that we are writing a library of utility functions that should run not only in the browser, but also in an environment such as Node.

root

Whereas underscore is not so simple, we start with var root = this.

The reason I’m writing this is because we’re going to get the global object from this, and then we’re going to mount the _ object onto it.

While in strict mode, this returns undefined instead of pointing to Window. Fortunately, underscore does not use strict mode, but even so, it is unavoidable because in ES6 module scripts automatically adopt strict mode, regardless of whether or not they declare use strict.

If this returns undefined, the code will report an error, so the idea is to check the environment and mount it to the correct object. Let’s modify the code:

var root = (typeof window= ='object' && window.window == window && window) | | (typeof global == 'object' && global.global == global && global);Copy the code

In this code, we determine the browser and Node environment, but are these the only two environments? Let’s look at Web workers.

Web Worker

Web workers belong to HTML5. To quote from the Authoritative Guide to JavaScript:

In the Web Worker standard, it is defined to solve the problem of client JavaScript not being able to multithread. The “worker” defined here refers to the parallel process of executing code. However, the Web Worker is in a self-contained execution environment, unable to access Window objects and Document objects, and communication with the main thread can only be achieved through asynchronous messaging mechanism.

To demonstrate the effects of a Web Worker, I wrote a demo and looked at the code.

In Web workers, the Window object is not accessible, so the result of typeof Window and typeof global is undefined, so root is false. Adding attributes and methods to a value of a primitive type like an object naturally raises an error.

So what do we do?

While the Window object cannot be accessed in the Web Worker, we can access the global object in the Worker environment through self. We’re just looking for the global variable to mount, so we can hook it into self.

And in the browser, in addition to the window property, we can also access the Winow object directly through the self property.

console.log(window.window === window); // true
console.log(window.self === window); // trueCopy the code

Considering the additional support for Web workers with self, we’ll just change the code to self:

var root = (typeof self == 'object' && self.self == self && self) ||
           (typeof global == 'object' && global.global == global && global);Copy the code

node vm

There is no window or global variable in node vm module, sandbox module. Look at the code.

However, we can access the global object through this, so someone initiated a PR that changed the code to:

var root = (typeof self == 'object' && self.self == self && self) ||
           (typeof global == 'object' && global.global == global && global) ||
           this;Copy the code

Wechat applets

Here, or not, it is the turn of micro channel small program.

Because in wechat applet, both window and global are undefined, plus the strict mode is forced to use, this is undefined, the mount error will occur, so someone sent a PR again, the code becomes:

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

This is what V1.8.3 looks like now.

Although the author can explain the final code directly, but the author want to with you look at how this seemingly ordinary code is turned into such a step by step, also want to tell you that the robustness of the code, didn’t happen overnight, but has gathered a lot of experience, we considered many unexpected places, it is one of the many benefits of open source projects.

The function object

Now we move on to the second sentence var _ = {}; .

If the underscore is set only to an empty object, we can only call methods using ‘.reverse(‘hello’) ‘. In fact, underscore also supports calls that resemble object-oriented approaches:

_ ('hello').reverse(); // 'olleh'Copy the code

Here’s another example comparing the next two calls:

// Functional style
_.each([1.2.3].function(item){
    console.log(item)
});

// Object-oriented style_ ([1.2.3]).each(function(item){
    console.log(item)
});Copy the code

But how?

Since it can be executed as _([1, 2, 3]), it shows that _ is not a literal object, but a function!

Luckily, in JavaScript, functions are also objects. Here’s an example:

var _ = function() {};
_.value = 1;
_.log = function() { return this.value + 1 };

console.log(_.value); / / 1
console.log(_.log()); / / 2Copy the code

We can completely define custom functions on _ functions!

It is currently written as:

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

root._ = _;Copy the code

How to do _([1, 2, 3]).each(…) ? That is, how does a function return an object, and how does that object call a method that hangs on the function?

Let’s look at how underscore is implemented:

var _ = function(obj) {
    if(! (this instanceof _)) return new _(obj);
    this._wrapped = obj; }; _ ([1.2.3]);Copy the code

_([1, 2, 3])

  1. performthis instanceof _, this points to window,window instanceof _To false,!The operator is reversed, so executenew _(obj).
  2. new _(obj), this refers to the instance object,this instanceof _When true, the code continues to execute
  3. performthis._wrapped = obj, the function completes
  4. Summing up,_ ([1, 2, 3])Returns an object, which is{_wrapped: [1, 2, 3]}, the object’s prototype points to _. Prototype

The schematic diagram is as follows:

_ () diagram

The problem is that the method is mounted on the _ function object, not the prototype of the function, so the returned instance cannot actually call the method on the _ function object.

Let’s write an example:

(function(){
    var root = (typeof self == 'object' && self.self == self && self) ||
               (typeof global == 'object' && global.global == global && global) ||
               this| | {};var _ = function(obj) {
        if(! (this instanceof _)) return new _(obj);
        this._wrapped = obj;
    }

    root._ = _;

    _.log = function(){
        console.log(1)
    }

})()

_().log(); / / _ (...). .log is not a functionCopy the code

It does, so we need another method to copy the method on _ to _. Prototype, and that method is _. Mixin.

_.functions

To copy the methods on _ to the prototype, we first need to get the methods on _, so we write an _. Functions method first.

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

The isFunction function can be seen in the JavaScript Topic type Judgment (part 2).

_.mixin

Now we can write mixin methods.

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

Each method can refer to the implementation of JavaScript Thematic jQuery Universal traversal method each.

Note that we can extend custom methods for underscore because _[name] = obj[name] :

_.mixin({
  addOne: function(num) {
    return num + 1;
  }
});

_(2).addOne(); / / 3Copy the code

At this point, we have achieved support for both object-oriented and functional styles.

export

Root._ = _

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

To support modularity, we need to export _ as a module in the appropriate environment, but the API of the NodeJS module has changed, for example in earlier versions:

// add.js
exports.addOne = function(num) {
  return num + 1
}

// index.js
var add = require('./add');
add.addOne(2);Copy the code

In the new version:

// add.js
module.exports = function(1){
    return num + 1
}

// index.js
var addOne = require('./add.js')
addOne(2)Copy the code

Exports = module.exports = _ Exports = module = _ Exports = module = _ Exports = module = _ exports = module = _

Exports is a reference to module.exports in nodejs. When you use module.exports = function(){}, that actually overrides module.exports, However, exports did not change. In order to avoid correct export caused by modification of exports later, it was written like this to maintain the unification of the two.

Write a demo:

First demo:

// exports is a reference to module.exports
module.exports.num = '1'

console.log(exports.num) / / 1

exports.num = '2'

console.log(module.exports.num) / / 2Copy the code

Second demo:

// addOne.js
module.exports = function(num){
    return num + 1
}

exports.num = '3'

// add addone.js to result.js
var addOne = require('./addOne.js');

console.log(addOne(1)) / / 2
console.log(addOne.num) // undefinedCopy the code

Third demo:

// addOne.js
exports = module.exports = function(num){
    return num + 1
}

exports.num = '3'

// add addone.js to result.js
var addOne = require('./addOne.js');

console.log(addOne(1)) / / 2
console.log(addOne.num) / / 3Copy the code

Why an exports.nodetype judgment at last? This is because if you add an element with the id of exports to your HTML page, for example

<div id="exports"></div>Copy the code

This generates a window.exports global variable that you can print directly from the browser command line.

At this point in the browser, Typeof Exports! = ‘undefined’, then exports._ = _, whereas in the browser we need to mount _ to global variables, so here we need to do a DOM node check.

The source code

The final code looks like this. With this basic structure, you are free to add any functions you need to use:

(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.1';

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

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

A link to the

  1. “JavaScript Topics for Typing (Part 2)”

  2. Implementation of jQuery Universal Traversal Method each

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.