This is the 19th day of my participation in the Genwen Challenge

Let’s start by looking at why your code needs to be modular. The goal of modularizing code is to be able to assemble code modules from different sources into large programs. Each module encapsulates and hides its own implementation details, and there is no coupling between modules.

Through modular programming, developers only need to care about the implementation of their modules, and only need to care about the interface methods exposed when using other modules, which can greatly improve programming efficiency and project maintenance costs, and improve the readability and reusability of the code.

Prior to ES6, JavaScript did not have built-in support for modules, and the common form of modularity in practice was based on closures. Later, the Node programming environment provided an implementation of modules based on require(), but was not officially adopted by JavaScript. Until ES6, export, import, and import() modules were used.

Module implementation based on closures

We know that variables and functions declared in a function are only in the scope of the function, and are private to the function and cannot be affected outside of it. This is how closures work. By means of closures, you can encapsulate the implementation details of your code and return only the public apis that need to be exposed to the outside world, thereby modularizing your code.

The following code implements a statistical module by means of closures, exposing only the mean() and stddev() functions for external use and hiding implementation details.

const stats = (function() {
    // Module private helper functions
    const sum = (x, y) = > x + y;
    const square = x= > x * x;

    // The public API to export
    function mean(data) {
        return data.reduce(sum)/data.length;
    }
    function stddev(data) {
        let m = mean(data);
        return Math.sqrt(
            data.map(x= > x - m)
                .map(square)
                .reduce(sum)/(data.length-1)); }// Export the public API as an object property
    return { mean, stddev }
}())

// Use the STATS module without worrying about implementation details
stats.mean([1.3.5.7.9])    / / = > 5
stats.stddev([1.3.5.7.9])  / / = > 3.1622776601683795
Copy the code

Modules in Node

Modules in Node, also known as CommonJS modules, are based on the CommonJS specification. Exports module apis using exports or module.exports and imports modules using require().

export

In Node, each file is a Module, internally defining a global Module object and an exports variable. Can output to see:

console.log(module); / / give it a try
console.log(module.exports);   / / {}
console.log(exports= = =module.exports);  // true
Copy the code

Write a STATS module, exported through exports.

// stats.js
const sum = (x, y) = > x + y;
const square = x= > x * x;

exports.mean = data= > data.reduce(sum)/data.length;
exports.stddev = function(d) {
    let m = exports.mean(d);
    return Math.sqrt(d.map(x= > x - m).map(square).reduce(sum)/(d.length-1));
};
Copy the code

Exports are also available through module.exports.

module.exports = {
    mean,
    stddev
}
Copy the code

The import

Node uses the require() method to import modules. If it is a built-in module or an installed module, import it directly by name. If it is a module of your own code, import it by file relative path.

// index.js
const fs = require('fs'); // Built-in file system module
const express = require('express'); // Installed modules
const stats = require('./stats.js'); // Project code module
Copy the code

Output the module under the current file and see. If you printed module in the previous section, you will now see several more module objects in the Children array. The required() method simply stores the imported module in the children array of the current Module object.

The basic principle is similar to this code:

const modules = {};
function require(moduleName) { return modules[moduleName]; }

modules['stats.js'] = (function() {
    const exports = {};
    
    // The contents of the stats.js file
    const sum = (x, y) = > x + y;
    const square = x= > x * x;
    exports.mean = function(data) {... };exports.stddev = function(d) {... };return exports; } ());Copy the code

Modules in ES6

ES6 adds the import and export keywords to JavaScript for importing and exporting modules.

export

To export constants, variables, functions, or classes from ES6 modules, use the export keyword.

export const PI = Math.PI;
export function degreesToRadians(d) { return d * PI / 180; }

// Or export curly braces. Note that this is not an object
export { PI, degreesToRadians }

// Export by default
export {
    mean,
    stddev
}
Copy the code

Export is valid only for declarations with names, and any expression can be exported using the default export of Export Default. The two are generally not used together, but there is no prohibition against such use.

The import

The import module is implemented using the import keyword.

// Import the default export, which needs to be named
import stats from './stats.js'

// Non-default exports have names and can only be referred to by name
import { PI, degressToRadians } from './stats.js';
// You can change your name
import { PI as pi } from './stats.js';
// Can also be assigned to the same object
import * as stats from './stats.js';

// If there are both default exports and non-default exports, you can import them in this way
import stats, { PI, degressToRadians } from './stats.js';

// Import modules without any exports
import './analytics.js';
Copy the code

Import a module without any exports. The code for that module runs once on the first import, after which the import does nothing. Used in special cases. (For example, if you want to execute the code but don’t need the exported value).

In addition, ES2020 introduces dynamic import import(). Passing a module identifier to import() returns a promise like this:

import('./stats.js').then(stats= > {
    let average = stats.mean(data);
})

// Or use async functions
async analyzeData(data) {
    let stats = await import('./stats.js');
    return {
        average: stats.mean(data),
        stddev: stats.stddev(data)
    }
}
Copy the code

Note that import() looks like a function call, but it is really an operator, and the parentheses represent the required parts of the operator’s syntax.

Comparison between Node modules and ES6 modules

After understanding the Node module and ES6 module respectively, we conducted a more in-depth analysis and comparison of them. The syntax is different and the API is different.

Node module exports are one object, while ES6 modules are multiple API exports.

Node modules are dynamically imported and can be written anywhere. ES6 modules are static imports and can only be written at the top level (imports are promoted by variables).

The ES6 module uses import to import a variable that is read-only and cannot be assigned a value. The second imported variable is passed by reference and is bound to the original variable. If it is changed, other places where the module was imported will also be affected. The Node module imports a shallow copy of the value.

The relevant data

The Definitive JavaScript Guide

Require () source code interpretation

Dive into CommonJs and ES6 Modules

CommonJS