Skip to main content

模块规范

以下是对 AMD、CMD、UMD、CommonJS 和 ESModule 五大 JavaScript 模块规范的系统性对比和解析:

一、CommonJS(服务器端模块规范)

核心特点

  • 同步加载:模块在使用时同步加载(适合服务器,文件在本地磁盘)。
  • 模块缓存:首次加载后缓存,后续引用直接返回缓存结果。
  • 导出机制:导出值的拷贝(非引用)。

语法示例

// 导出模块(math.js)
const add = (a, b) => a + b;
module.exports = { add };

// 导入模块
const math = require('./math');
console.log(math.add(1, 2)); // 3

应用场景

  • Node.js 后端开发。
  • 不需要考虑异步加载的环境。

二、AMD(Asynchronous Module Definition)

核心特点

  • 异步加载:依赖提前声明,异步并行加载。
  • 代表实现:RequireJS。
  • 浏览器优先:专为浏览器环境设计,避免同步加载阻塞渲染。

语法示例

// 定义模块(math.js)
define(['dependency1', 'dependency2'], function(dep1, dep2) {
const add = (a, b) => a + b;
return { add };
});

// 加载模块
require(['math'], function(math) {
console.log(math.add(1, 2));
});

执行流程

  1. 解析依赖数组,异步加载所有依赖。
  2. 所有依赖加载完成后,执行工厂函数。
  3. 缓存并返回模块导出值。

三、CMD(Common Module Definition)

核心特点

  • 延迟加载:依赖就近声明,按需加载。
  • 代表实现:Sea.js。
  • 语法接近 CommonJS:更符合“同步思维”。

语法示例

// 定义模块(math.js)
define(function(require, exports, module) {
const dep1 = require('./dependency1'); // 按需加载
const add = (a, b) => a + b;
exports.add = add;
});

// 使用模块
seajs.use(['math'], function(math) {
console.log(math.add(1, 2));
});

与 AMD 的区别

  • AMD:依赖前置,推崇依赖预加载。
  • CMD:依赖就近,推崇依赖延迟加载。

四、UMD(Universal Module Definition)

核心特点

  • 通用模块规范:兼容 CommonJS、AMD 和全局变量。
  • “if-else” 检测逻辑:自动判断当前环境并选择合适的加载方式。

语法示例

(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD 环境
define(['dependency'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS 环境
module.exports = factory(require('dependency'));
} else {
// 全局变量环境
root.MyModule = factory(root.dependency);
}
})(this, function(dep) {
// 模块实现
return {
// 导出内容
};
});

应用场景

  • 编写需要在多种环境运行的库(如 jQuery 插件)。
  • 向后兼容旧有系统。

五、ESModule(ES6 标准模块规范)

核心特点

  • 静态导入/导出:编译时确定依赖关系(支持 Tree Shaking)。
  • 原生支持:浏览器和 Node.js(需配置)均支持。
  • 导出引用:导出的是值的引用(非拷贝)。

语法示例

// 命名导出
export const add = (a, b) => a + b;

// 默认导出
export default { add };

// 导入方式
import { add } from './math.js'; // 命名导入
import math from './math.js'; // 默认导入

高级特性

  • 动态导入import('./module.js').then(...)
  • 静态分析:支持编译时检查和优化。

六、五大规范对比表

规范加载方式应用场景语法特点典型工具
CommonJS同步加载Node.js 服务器端require/exportsNode.js
AMD异步预加载浏览器端(RequireJS)define/require(依赖前置)RequireJS
CMD异步延迟加载浏览器端(Sea.js)define/require(依赖就近)Sea.js
UMD自适应环境跨平台库(兼容多种规范)复杂的环境检测逻辑手动编写或工具生成
ESModule静态分析 + 动态加载现代浏览器和 Node.jsimport/export原生支持,Webpack 打包

七、关键差异解析

1. 同步 vs 异步加载

  • CommonJS:同步加载模块,适合服务器(文件在本地磁盘)。
  • AMD/CMD/ESModule:异步加载模块,适合浏览器(避免网络请求阻塞)。

2. 静态 vs 动态分析

  • ESModule:静态导入/导出(编译时确定依赖),支持 Tree Shaking。
  • CommonJS/AMD/CMD:动态导入(运行时确定依赖),无法静态优化。

3. 导出机制

  • CommonJS:导出值的拷贝(修改原变量不影响导出值)。
  • ESModule:导出值的引用(修改原变量会影响导出值)。

八、现代前端实践

  1. 浏览器端

    • 优先使用 ESModule(通过 <script type="module">)。
    • Webpack 等工具处理模块打包和兼容性。
  2. Node.js 端

    • 传统项目使用 CommonJS(.js 文件)。
    • 新项目可配置使用 ESModule(.mjspackage.json"type": "module")。
  3. 跨平台库

    • 使用 UMD 或直接提供 ESModule + CommonJS 双版本(如 Lodash)。

九、常见面试问题

  1. ESModule 与 CommonJS 的核心区别是什么?

    • 答:ESModule 静态导入、导出引用、支持 Tree Shaking;CommonJS 动态导入、导出值拷贝、不支持静态优化。
  2. 为什么 AMD 需要在浏览器中使用,而 CommonJS 不适合?

    • 答:浏览器加载模块需通过网络请求,同步加载会阻塞渲染;AMD 异步加载避免了这个问题。
  3. 如何实现一个支持多种模块规范的库?

    • 答:使用 UMD 包装,或提供 ESModule 和 CommonJS 双版本,通过 package.jsonmainmodule 字段指定入口。
  4. Tree Shaking 的原理是什么?

    • 答:依赖 ESModule 的静态结构,编译时分析哪些导出未被使用,从而剔除冗余代码。

十、总结

  • CommonJS:服务器端标准,同步加载。
  • AMD/CMD:早期浏览器异步方案,已逐渐被 ESModule 取代。
  • UMD:兼容多种环境的过渡方案。
  • ESModule:未来趋势,统一前后端模块语法,支持现代优化工具链。

理解各规范的设计初衷和适用场景,有助于在实际开发中做出合理选择,并有效解决模块兼容性问题。