One way of improving the structure and organization of our programs is to break them into smaller, relatively loosely coupled segments called modules.

At a bare minimum, each module system should be able to do the following:

  1. Define an interface through which we can access the functionality offered by the module.
  2. Hide module internals so that the users of our modules aren’t burdened with a whole host of unimportant implementation details.
    • In addition, by hiding module internals, we protect those internals from the outside world, thereby preventing unwanted modifications that can lead to all sorts of side effects and bugs.

Node.js has two module systems: CommonJS modules and ECMAScript modules (esm).

  • Authors can tell Node.js to use the ECMAScript modules loader via the .mjs file extension, the package.json "type" field, or the --input-type flag.
  • Outside of those cases, Node.js will use the CommonJS module loader.

Pre-ES6 JavaScript

Module Pattern

Pre-ES6 JavaScript has only two types of scopes: global scope and function scope. It doesn’t have something in between, a namespace or a module that would allow us to group certain functionality together.

const MouseCounterModule = function() {
    let numClicks = 0
    const handleClick = () => {
        alert(++numClicks)
    }
    return {
        countClicks: () => {
            document.addEventListener("click", handleClick)
        }
    }
}()

// augmenting a module
(function (module) {
    let numScrolls = 0
    const handleScroll = () => {
        alert(++numScrolls)
    }
    module.countScrolls = () => {
        document.addEventListener("wheel", handleClick)
    }
})(MouseCounterModule)
  • IIFE 用来创建新的函数作用域,隔离里面定义的变量,并用闭包使它们可以被操作;
  • IIFE 返回一个新的对象作为模块的公共接口。
  • 在返回的对象基础上进行属性的添加就可以增加功能。
    • 由于在不同的函数作用域中,增强的对象和原对象不能共享私有变量([[Environment]] 不同)。

This pattern of using immediate functions, objects, and closures to create modules in JavaScript is called the module pattern.

When we start building modular applications, the modules themselves will often depend on other modules for their functionality. Unfortunately, the module pattern doesn’t cover the management of these dependencies. We, as developers, have to take care of the right dependency order so that our module code has all it needs to execute.

To deal with these issues, a couple of competing standards have arisen, namely Asynchronous Module Definition (AMD) and CommonJS.

AMD and CommonJS

Besides some differences in syntax and philosophy, the main difference is that AMD was designed explicitly with the browser in mind, whereas CommonJS was designed for a general-purpose JavaScript environment (such as servers, with Node.js), without being bound to the limitations of the browser.

AMD(Asynchronous Module Definition):适用于浏览器,模块异步加载。

define('MouseCounterModule',['jQuery'], $ => {
    let numClicks = 0;
    const handleClick = () => {
        alert(++numClicks);
    };
    return {
        countClicks: () => {
            $(document).on("click", handleClick);
        }
    };
});
  • Multiple modules can be defined within one file.
  • Automatic resolving of dependencies, so that we don’t have to think about the order in which we include our modules.

CommonJS:同步加载模块,适合服务器端用(Node.js,读取本地文件操作);语法简单。

  • To each module, CommonJS exposes a variable, module, with a property, exports, which we can easily extend with additional properties. In the end, the content of module.exports is exposed as the module’s public interface.

    // MouseCounterModule.js
    const $ = require("jQuery");
    let numClicks = 0;
    const handleClick = () => {
        alert(++numClicks);
    };
    module.exports = {
        countClicks: () => {
            $(document).on("click", handleClick);
        }
    };
    
    const MouseCounterModule = require("MouseCounterModule.js");
    MouseCounterModule.countClicks();
    
    • Only variables and functions exposed through the module.exports object are available from outside the module.
  • Because the philosophy of CommonJS dictates one module per file, any code that we put in a file module will be a part of that module.

    • Therefore, there’s no need for wrapping variables up in immediate functions.
    • All variables defined within a module are safely contained within the scope of the current module and don’t leak out to the global scope.
  • CommonJS is the default module format for Node.js.

  • CommonJS’s biggest disadvantage is that it wasn’t explicitly built with the browser in mind.

    • Within JavaScript in the browser, there’s no support for the module variable and the export property; we have to package our CommonJS modules into a browser-readable format.

Universal Module Definition, or UMD is a pattern with a somewhat convoluted syntax that allows the same file to be used by both AMD and CommonJS.

ES6 Modules


References