好的,这是一个关于 JavaScript 模块化规范的全面解析。这些规范的出现都是为了解决 JavaScript 在大型项目中代码组织、依赖管理和作用域隔离的问题。
下面我将详细解释 CommonJS、AMD、CMD、UMD 和 ESM,并说明它们之间的关系和区别。
1. CommonJS - 同步的服务器端模块规范
CommonJS 规范主要应用于服务器端,例如 Node.js 环境。它的设计理念是模块的加载是同步的,因为在服务器端,模块文件都存放在本地磁盘,读取速度非常快,同步加载不会造成性能问题。
特点:
- 同步加载:执行
require
时,会阻塞后续代码的执行,直到模块被加载并执行完毕。 - 模块输出的是值的拷贝:一旦输出一个值,模块内部的变化就影响不到这个值(除非是导出一个函数,通过函数来获取内部状态)。
- 每个文件都是一个模块:拥有独立的作用域。
语法:
- 导出模块:使用
module.exports
或exports
// math.js function add(a, b) {return a + b; } module.exports = {add: add }; // 或者 exports.add = add;
- 导入模块:使用
require
// app.js const math = require('./math.js'); console.log(math.add(2, 3)); // 5
适用环境: Node.js。
2. AMD - 异步的浏览器端模块规范
AMD 规范主要为了解决浏览器环境的模块化问题。由于浏览器加载文件需要从服务器获取,如果采用同步方式会导致页面“假死”,因此 AMD 的设计理念是异步加载。
代表库: RequireJS
特点:
- 异步加载:不阻塞页面渲染,依赖模块加载完毕后执行回调函数。
- 显式地声明依赖。
语法:
- 定义模块:使用
define
// 定义一个依赖 jQuery 的模块 define(['jquery'], function($) {// 模块功能function myFunc() {$('#content').html('Hello, AMD!');}// 暴露公共接口return {myFunc: myFunc}; });
- 加载模块:使用
require
// 加载并使用模块 require(['myModule'], function(myModule) {myModule.myFunc(); });
适用环境: 浏览器。
3. CMD - 通用的模块规范
CMD 也是国内发展出来的一种浏览器端模块规范,结合了 CommonJS 和 AMD 的特点。
代表库: Sea.js
特点:
- 推崇依赖就近、延迟执行。在需要依赖时,随时
require
。 - 语法上更接近 CommonJS。
语法:
- 定义模块:使用
define
define(function(require, exports, module) {// 同步获取依赖var $ = require('jquery');// 异步获取依赖require.async('./moduleB', function(moduleB) {// ...});// 暴露接口exports.someMethod = function() {$('#content').html('Hello, CMD!');}; });
AMD 与 CMD 的主要区别:
- 依赖声明:AMD 推崇依赖前置,在定义模块时就声明所有依赖。CMD 推崇依赖就近,在代码需要时再
require
。 - 执行时机:AMD 的模块加载完成后会立即执行。CMD 的模块加载后默认不执行,只有在
require
时才会执行。
4. UMD - 通用模块定义
UMD 不是一个独立的规范,而是一种模式或者代码包装技术。它的目标是让同一个模块代码可以在多种环境中运行(无论是 CommonJS、AMD 还是全局变量)。
原理: 通过判断当前环境支持的模块系统,将模块“适配”到该系统中。
示例模板:
(function (root, factory) {if (typeof define === 'function' && define.amd) {// AMD 环境 (如 RequireJS)define(['jquery'], factory);} else if (typeof module === 'object' && module.exports) {// CommonJS 环境 (如 Node.js)module.exports = factory(require('jquery'));} else {// 浏览器全局变量root.myModule = factory(root.jQuery);}
}(this, function ($) {// 模块的业务代码function myFunc() {$('body').html('Hello, UMD!');}// 暴露公共接口return {myFunc: myFunc};
}));
适用环境: 同时需要支持浏览器和 Node.js 的库,例如 React、Vue 等。
5. ESM - 官方的模块标准
ESM 是 ECMAScript 2015 (ES6) 官方推出的模块标准,旨在成为浏览器和服务器通用的模块解决方案。它是 JavaScript 语言的原生模块系统。
特点:
- 静态化:在编译时(代码执行前)就能确定模块的依赖关系,以及输入和输出的变量。这使得静态分析、Tree Shaking(摇树优化)成为可能。
- 模块输出的是值的引用。
- 异步加载,也可以同步执行。
- 语法简洁,是未来的绝对主流。
语法:
- 导出模块:使用
export
/export default
// lib.js // 命名导出 export const pi = 3.14; export function add(a, b) { return a + b; }// 默认导出 export default function() { console.log('Hello'); }
- 导入模块:使用
import
// main.js // 导入命名导出 import { pi, add } from './lib.js'; // 导入默认导出 myDefault from './lib.js';console.log(pi); // 3.14 add(1, 2); myDefault();
在浏览器中使用:
需要在 <script>
标签中加上 type="module"
属性。
<script type="module" src="main.js"></script>
适用环境: 现代浏览器和现代 JavaScript 运行环境(Node.js 也已稳定支持)。
总结与对比
规范 | 主要环境 | 加载方式 | 特点 | 关键字 |
---|---|---|---|---|
CommonJS | Node.js | 同步 | 简单,适合服务器 | require , module.exports |
AMD | 浏览器 | 异步 | 依赖前置,适合浏览器 | define , require |
CMD | 浏览器 | 异步 | 依赖就近,延迟执行 | define , require |
UMD | 通用 | 均可 | 兼容多种环境的包装方案 | - |
ESM | 通用(现代) | 静态/异步 | 官方标准,静态分析,未来趋势 | import , export |
发展趋势
- ESM 是未来:随着 ES6+ 的普及和构建工具(Webpack、Vite、Rollup)的成熟,ESM 已经成为新项目的首选。
- CommonJS 在 Node.js 生态中依然庞大,但 Node.js 自身正在积极推动向 ESM 迁移。
- AMD/CMD 在现代前端开发中已很少使用,主要存在于一些遗留的老项目中。
- UMD 对于需要分发、兼容多种环境的库来说,仍然非常有用。
简单来说,对于现代开发:写库用 UMD 或 ESM 分发,写应用直接用 ESM。