JavaScript 模块化规范详解:CommonJS、AMD、UMD、CMD 与 ES6 对比

作者:青柠时间:2026-10-26 12:49:27浏览:358

本文深入解析 JavaScript 五大模块化规范:CommonJS、AMD、UMD、CMD 及 ES6。详细阐述各规范的加载机制、优缺点及适用场景,重点对比 AMD 与 CMD 的执行差异,以及 ES6 模块在静态分析与加载方式上与 CommonJS 的本质区别,帮助开发者根据项目需求选择最佳方案。

JavaScript 语言的发展历程

本文将系统介绍几种主流的 JavaScript 模块化规范,并分析它们各自的优缺点及核心差异。

JavaScript 最初仅作为简单的脚本语言存在,但随着浏览器功能的日益强大,其承载复杂应用的能力得到认可。为了便于代码维护,开发者开始将不同功能抽离至独立的 JS 文件中。然而,随着项目复杂度提升,HTML 中引入大量 JS 文件导致命名冲突、作用域污染等问题频发,模块化概念及实现方案因此应运而生。

模块化概述

模块化不仅是一种管理方式,更是一种生产模式和解决方案。一个模块通常对应实现特定功能的文件,开发者可以按需引入所需模块以复用他人代码。为确保模块间协同工作,必须遵循特定的规范,这催生了后续一系列标准化的模块系统。

1. CommonJS 规范

CommonJS 主要应用于 node 服务端开发环境。在 CommonJS 中,每个文件被视为一个独立的模块,拥有独立的作用域。模块通过 module.exports 机制向外暴露 public 成员。

此外,CommonJS 规范通过 require 函数引入模块依赖。require 函数可用于加载 node 内置模块、项目自定义模块以及通过 npm 管理的第三方模块。

定义模块示例:

// 定义模块 math.js
var basicNum = 0;
function add(a, b) {
  return a + b;
}
// 在这里写上需要向外暴露的函数、变量
module.exports = { 
  add: add,
  basicNum: basicNum
}

加载模块示例:

// 引入 math.js 模块
var math = require('./math');
math.add(2, 3); // 5

CommonJS 的优点:

  • 语法简单且易于使用
  • 服务端模块复用性高

CommonJS 的缺点:

  • 同步加载机制不适合浏览器环境
  • 无法实现非阻塞的并行模块加载

2. AMD 规范

AMD 全称为 (Asynchronous Module Definition),即“异步模块定义”。该规范采用异步方式加载模块,模块加载过程不会阻塞后续代码的执行。所有依赖该模块的语句均封装在回调函数中,待模块加载完成后,回调函数才会执行。

在 AMD 规范中,我们使用 define 定义模块,使用 require 加载模块,但是不同于 CommonJS,它要求两个参数:

定义模块语法:

define(id?, dependencies?, factory);
  • id 为可选的模块名称。若未提供,模块名默认为加载器请求的脚本名;若提供,则必须为顶级绝对路径。
  • dependencies 为可选的依赖模块数组。依赖模块按数组顺序优先执行,其执行结果依次作为参数传入 factory。
  • factory 为模块初始化执行的函数或对象,是必需参数。

加载模块语法:

require([module], callback);

第一个参数 module 为包含待加载模块的数组;第二个参数 callback 为加载成功后的回调函数。若将前述 CommonJS 写法改写为 AMD 形式,代码如下:

require(['./math'], function (math) {
  math.add(2, 3);
});

AMD 的优点:

  • 适合浏览器环境下的异步模块加载
  • 支持并行加载多个模块

AMD 的缺点:

  • 增加了开发成本
  • 不符合通用的模块化思维习惯

3. UMD 规范

UMD 全称为 (Universal Module Definition),即“通用模块定义”。UMD 是 AMD 与 CommonJS 的混合体。AMD 侧重浏览器优先与异步加载;CommonJS 侧重服务器优先与同步加载。

为实现通用性,UMD 策略如下:首先判断是否支持 node 模块环境,若支持则采用 node 方式;其次判断是否支持 AMD,若支持则采用 AMD 方式加载。这种兼容多种规范的策略被称为 UMD。

UMD 示例代码:

(function (window, factory) {
  if (typeof exports === "object") {
    // CommonJS
    module.exports = factory();
  } else if (typeof define === "function" && define.amd) {
    // AMD
    define(factory);
  } else {
    // 浏览器全局定义
    window.eventUtil = factory();
  }
})(this, function () {
  // do something
});

4. CMD 规范

CMD 全称为 (Common Module Definition),即“公共模块定义”。CMD 可能是在 CommonJS 基础上抽象出的一套模块化语法定义与使用标准。

在 CMD 规范中,一个文件对应一个模块。

定义模块语法:

define(factory);

define 接收 factory 参数,该参数可以是函数、对象或字符串。

  • 当 factory 为对象或字符串时,表示该模块的接口即为该对象或字符串。
  • 当 factory 为函数时,表示该模块的构造方法。执行该构造方法可获得模块对外提供的接口。factory 执行时默认传入三个参数:require、exports 和 module。其中 require 用于加载其他模块,exports 用于向外提供模块接口,module 为存储当前模块相关属性和方法的对象。

加载模块方式:

可通过 SeaJs 的 use 方法加载模块,语法如下:

seajs.use([module], callback);

CMD 的优点:易于在 node 环境中运行

CMD 的缺点:依赖 SPM 打包工具,模块加载逻辑相对复杂

5. ES6 模块化

ES6 模块的设计思想强调“静态化”,旨在编译阶段即可确定模块依赖关系及输入输出变量。相比之下,CommonJS 和 AMD 模块需在运行时才能确定这些内容。

在 ES6 中,使用 export 关键字导出模块,使用 import 关键字引入模块。

引入模块示例:

// ES6模块
import { stat, exists, readFile } from 'fs';

上述代码实质是从 fs 模块中加载 3 个方法,其余方法不加载。这种加载方式称为“编译时加载”或“静态加载”,即 ES6 可在编译阶段完成模块加载,效率高于 CommonJS 模块的加载方式。这也导致无法直接引用 ES6 模块本身,因为其并非对象。

导出模块示例:

let firstName = 'Zhou';
let lastName = 'ShuYi';
let year = 1994;
export { firstName, lastName, year };

上述代码在 export 后使用大括号指定输出的一组变量。export 除输出变量外,还可输出函数或类。

ES6 模块的优点:易于进行静态分析

ES6 模块的缺点:原生浏览器端尚未完全实现该标准

AMD 与 CMD 的区别

对于依赖模块,AMD 采用“提前执行”策略,而 CMD 采用“延迟执行”策略。

AMD 推崇“依赖前置”,CMD 推崇“依赖就近”。

AMD 的 API 默认一个函数处理多种情况,CMD 的 API 严格区分,推崇职责单一。

ES6 模块与 CommonJS 模块的差异

CommonJS 模块输出的是“值的拷贝”,而 ES6 模块输出的是“值的引用”。

CommonJS 模块采用“运行时加载”,ES6 模块采用“编译时输出接口”。

CommonJS 模块的 require() 命令为“同步加载”,而 ES6 模块的 import 命令为“异步加载”,具有独立的模块依赖解析阶段。

总结

以上为笔者对 JavaScript 模块化规范的解读,更多关于 JS 模块化规范的资料请关注本站其他相关文章!

作者最新文章