Skip to content

js模块标准总结

发布日期:2020-12-25

js 模块化不仅是一个工程问题,也有很多历史原因,见证了 js 的发展。远古时代的 js 没有模块化,全是在 script 标签中引入的全局变量。社区就开始为 js 实现各种模块化标准来实现,于是有了 AMD 和 CMD。nodejs 的出现带来了 CJS,为了实现浏览器和 node 都可以使用的模块化规范,于是有了 UMD。直到 ES6 原生支持 js 的模块化,并被越来越多浏览器支持,ESM 才成为了 js 模块化的主流。

script 标签

浏览器的 script 标签是同步下载并执行的,会阻塞渲染引擎。在前端的原始时代,几乎所有的工具库都是在 script 标签中全局引入,在浏览器全局对象中增加一个对象,比如引用 jq 后全局对象可用 $ 来访问。有两个属性可以改变这一默认行为,defer是“渲染完再执行”,async是“下载完就执行”,这个知识点在某大厂面试中有考察到。

模块化

最常见的两种模块规范是 CJS 和 ESM,以下是主要差异。

  • CommonJS 模块输出的是一个值的拷贝(基础类型是拷贝,引用类型是浅拷贝),ES6 模块输出的是值的引用(基础类型也是引用)。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

CJS

nodeJs默认的模块标准,是一种同步加载模块的方案。

// 顶级模块导出,会覆盖次级模块导出,因为 module.exports 和 exports 所指向的内存地址相同。
module.exports = function doSomething(n) {
  // do something
}
// 次级模块导出
exports.a = 1

// 顶级模块引入
const doSomething = require('./doSomething.js')

// 次级模块引入
const { a } = require('./a.js')
  • 对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值;对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
  • 当使用require命令加载某个模块时,就会运行整个模块的代码。(运行时加载)
  • 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
  • 循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

ESM

ECMAScript 6 (ES6)中引入了模块功能,是 js 原生支持的模块规范。

import vue from 'vue'

export default {}

export const a = 1

.mjs文件总是以 ES6 模块加载,.cjs文件总是以 CJS 模块加载。

AMD (Asynchronous Module Definition) 异步模块定义

不同与CJS,AMD是一种异步加载模块的方案。

require.js 是 AMD 的一种实现,必须要有这些实现才能使用这个模块规范,因为浏览器中并没有 define。

// 依赖前置,提前执行
define(['dep1', 'dep2'], function (dep1, dep2) {
    // Define the module value by returning a value.
    return function () {};
});

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。

CMD (Common Module Definition) 通用模块定义

seaJS 是 CMD 的一种实现。

// 定义 a.js 模块,同时可引入其他依赖模块,及导出本模块
define(function(require, exports, module) {
  var $ = require('jquery.js') // 用到的时候再 require
  exports.price = 200;  
});

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。 CMD 要解决的问题与 AMD 一样,只不过是对依赖模块的执行时机不同。

  1. AMD 是提前执行,CMD 是延迟执行。
  2. AMD 是依赖前置,CMD 是依赖就近。

UMD (Universal Module Definition) 通用模块定义

支持两种风格(CJS 和 AMD)的通用模式, 可以同时适用于前后端。

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS-like
        module.exports = factory(require('jquery'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.jQuery);
    }
}(this, function ($) {
    //    methods
    function myFunc(){};

    //    exposed public method
    return myFunc;
}));

参考文章 js模块化编程不完全指北一文彻底搞懂JS前端5大模块化规范及其区别SeaJS 与 RequireJS 的异同

Power by vitepress