【JS】【前端】JavaScript 模块化演进之路(CJS/AMD/UMD/CMD/ESM)
CommonJS
CommonJS 是 JavaScript 在服务器端(Node.js)的一种 模块规范。
在浏览器之外的环境(比如服务器)中,JavaScript 需要一种方式来组织代码、管理依赖和导出功能。CommonJS 就是为了解决这个问题而诞生的。
核心概念
CommonJS 的精髓可以概括为三个关键词:require、module 和 exports
- 导入(require): 使用
require()函数来加载模块 - 模块(module): 每个文件都是一个独立的模块,拥有自己的作用域
- 导出(exports): 使用
module.exports或exports对象来决定模块中哪些内容可以被外部访问
代码示例
假设我们有两个文件:
- math.js (定义模块)
constadd=(a, b)=> a + b;constPI=3.14;// 将需要公开的内容导出 module.exports ={ add,PI};- app.js (使用模块)
// 引入 math.js 模块const math =require('./math.js'); console.log(math.add(2,3));// 输出 5 console.log(math.PI);// 输出 3.14特点
- 同步加载: 模块是按顺序加载的。这对服务器端(文件存在磁盘上)没问题,但由于网络延迟,这种方式不适合浏览器,这也是后来出现 ESM(ES Modules)的原因。
- CJS 无法在浏览器中工作
- 运行时执行: 只有代码运行到
require这一行时,模块才会被加载和执行。 - 值拷贝: 当你导出一个变量时,外部获取的是这个值的一个快照(副本)。即使原模块里的变量后来变了,外部拿到的值也不会跟着变。
- 缓存: 模块第一次加载后会被缓存。再次
require同一个模块时,会直接从缓存读,不会重新运行代码。
AMD (Asynchronous Module Definition)
在 CommonJS 统治服务器端的同时,浏览器端遇到了挑战:由于网络环境限制,脚本不能像在本地磁盘那样同步加载。AMD 应运而生,它采取了“异步加载”策略。
特点
- 核心实现: 最著名的库是
RequireJS - 语法特征: 使用
define函数定义模块,使用require加载模块 - AMD 异步导入模块(因此得名)
- AMD 最初是为前端设计的(而 CJS 则用于后端)
- 我认为 AMD 和 CJS 正好相反。
示例
// math.js - 定义模块define([],function(){constadd=(a, b)=> a + b;return{ add };});// app.js - 使用模块require(['./math.js'],function(math){ console.log(math.add(2,3));// 输出 5});CMD (Common Module Definition)
CMD 全称 Common Module Definition(通用模块定义),是国内开发者主导的规范,依托 SeaJS 实现,同样面向浏览器端。它和 AMD 的目标一致,但风格更灵活,主打 “按需加载”
核心特点:就近依赖
CMD 最大的亮点是 “就近依赖”—— 不用在模块开头就声明所有依赖,而是用到哪个模块,再在哪个位置用require加载,就像走到半路需要什么再临时取一样
比如这样定义模块:
define(function(require, exports, module){// 用到时再加载依赖const dataService =require('./services/dataService');const uiUtils =require('./utils/uiUtils');functiondisplayUserInfo(){const userData = dataService.fetchUserData(); uiUtils.showMessage(userData.username);} exports.displayUserInfo = displayUserInfo;});这种方式的好处很明显:不用提前加载用不上的模块,减少初始加载资源;代码逻辑和依赖引入就近,读起来更顺,后续修改时改动范围也小,维护起来更轻松
UMD (Universal Module Definition)
随着模块化方案的割裂,开发者面临一个难题:我写的库是给 Node.js 用的(CommonJS),还是给浏览器用的?
UMD 的核心是 “环境判断”—— 加载模块时,先判断当前运行环境,再选择对应的模块化规范:
- 如果是 Node.js 环境,就用 CommonJS 的
module.exports和require; - 如果是支持 AMD 的浏览器环境,就用
define异步加载; - 如果是普通浏览器,就把模块挂载到
window全局对象上
示例
(function(root, factory){if(typeof define ==='function'&& define.amd){// AMD 环境define([], factory);}elseif(typeof module ==='object'&& module.exports){// CommonJS 环境 module.exports =factory();}else{// 浏览器全局环境 root.myLibrary =factory();}}(typeof self !=='undefined'? self :this,function(){return{add:(a, b)=> a + b };}));现在开发者很少手动写 UMD,但 Webpack 或 Rollup 等打包工具常生成 UMD 代码以确保最大的兼容性
ES Modules
ESM 是 ES Modules(ECMAScript Modules)的缩写。它是 JavaScript 官方在 2015 年(ES6)正式推出的模块化标准
在 ESM 出现之前,JavaScript 社区一直使用 CommonJS(Node.js)或 AMD 等非官方方案。ESM 的诞生是为了让 JavaScript 拥有原生的、统一的模块系统,使其既能在浏览器中运行,也能在服务器端运行
它不仅统一了标准,还带来了性能上的飞跃
- 语法特征:
import和export
示例
// math.jsexportconstadd=(a, b)=> a + b;exportconstPI=3.14;// app.jsimport{ add,PI}from'./math.js'; console.log(add(2,3));ESM 与 CommonJS 的区别对比
这是面试和实践中最常被提及的部分:
- 静态 vs 动态:
- ESM 是静态的:
import必须在文件顶层。这让编译器可以在运行代码前知道模块结构,从而实现 Tree Shaking(剔除没用到的代码)。 - CommonJS 是动态的:可以在
if语句里写require()。
- ESM 是静态的:
- 符号引用 (Live Bindings) vs 值拷贝:
- CommonJS 导出的是值的副本。
- ESM 导出的是值的引用。如果原模块里的变量变了,外部获取的值也会实时更新。
- 加载方式:
- ESM 原生支持异步加载,甚至可以支持 Top-level await。
对比
| 规范 | 加载方式 | 依赖处理 | 适用场景 | 核心语法示例 |
|---|---|---|---|---|
| CommonJS | 同步加载 | 运行时确定,require同步获取 | Node.js 服务器端开发 | const mod = require('./mod'); module.exports = {} |
| AMD | 异步加载 | 依赖前置,定义时声明依赖 | 浏览器端开发,需兼容复杂依赖场景 | define('mod', ['dep'], (dep) => {}); require(['mod'], (mod) => {}) |
| CMD | 异步加载 | 就近依赖,用到时require加载 | 浏览器端开发,追求代码灵活简洁 | define((require) => { const mod = require('./mod'); }) |
| UMD | 环境判断,按需同步 / 异步 / 挂载全局 | 融合 CommonJS、AMD 特点 | 跨平台库 / 框架(浏览器、Node.js 通用) | 见 示例的环境判断逻辑 |
| ESM | 静态加载(编译阶段确定) | import/export静态声明,只读 | 现代浏览器、Node.js 新项目,未来主流 | import { mod } from './mod.js'; export const x = 1; |
站在 2026 年的节点看,ES Modules 已经是绝对的赢家。无论是现代浏览器、最新的 Node.js 版本,还是 Deno/Bun 等新兴运行时,都将 ESM 作为一等公民。AMD 已成往事,而UMD 隐身于工具链后
参考
- https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm
- https://juejin.cn/post/7454030739781910537
- 《JavaScript 高级程序设计》第5版