JavaScript 五种模块系统详解:CommonJS、ESM、AMD、CMD 与 UMD
本文深入解析 JavaScript 中的五种核心模块系统:CommonJS、ES Modules (ESM)、AMD、CMD 及 UMD。详细对比了它们在加载时机、依赖管理、语法特性及适用场景上的差异,涵盖 Node.js 环境、浏览器端开发及打包工具配置,帮助开发者根据实际需求选择最合适的模块化方案。
模块系统
模块系统的基本概念在于将代码封装在独立文件中,通过声明导出接口,供其他文件导入并调用。
采用模块化开发具有以下显著优势:
- 变量作用域被限制在文件内部,避免污染全局命名空间;
- 代码依赖关系一目了然,通过文件顶部的导入语句即可清晰识别;
- 支持多人并行开发,各模块独立运行且互不干扰;
- 无需担心模块引入的顺序问题,构建工具会自动处理依赖解析;
- 便于以文件为单位进行独立的单元测试;
模块化机制有效解决了变量污染、代码维护困难及依赖顺序混乱等核心问题。
CommonJS
CommonJS(简称 CJS)是 Node.js 默认采用的模块化标准。
模块导出的实现方式如下:
// 集中一起导出
module.exports = {
userName: '前端西瓜哥',
// ...
}
// 或分散导出
module.exports.userName = '前端西瓜哥';
// 或
exports.userName = '前端西瓜哥';
每个文件都拥有一个 module 对象,其 exports 属性初始为空对象。开发者可以向该对象添加属性,module.exports 最终作为模块对外导出的内容。
exports 是 module.exports 的引用别名,使用它可以简化导出代码的书写。
模块导入的实现方式如下:
const Setting = require('./user');
// 或用解构写法,直接提取属性
const { userName } = require('./user');
// 或不使用任何导出内容,但希望指定对应模块文件的副作用(如给全局注入变量)
require('./user');
require 函数用于定位并加载指定模块文件,提取其 module.exports 对象并将其引入当前模块。关于 require 解析模块的具体路径逻辑较为复杂,此处暂不展开,后续可另行撰文详解。
目前 Node.js 最新版本已支持 ES Modules 模式,需在 package.json 文件中配置 "type": "module"。
ES Modules
ES Modules(简称 ESM)是 ES6 规范引入的新特性,也是 JavaScript 模块系统的正式标准。
在 script 标签中设置 type="module" 属性,即可启用浏览器端的模块化加载机制。
在实际生产环境中,通常不会直接在浏览器中运行 ESM 代码,而是使用 ESM 语法编写源码,再通过构建工具打包为传统格式运行。
模块导出的实现方式如下:
// 集中一起导出
export {
userName: '前端西瓜哥',
// ...
}
// 或分散指定
export const userName = '前端西瓜哥';
// 一种特殊的导出:默认指定
export default user;
在 ESM 中,export 并非一个对象或变量,而是一个新增的关键字,用于明确指定需要导出的变量或函数。
模块导入的实现方式如下:
// 导入需要的变量
import { userName } from './user.js';
// 导入 export default 对应的变量,这时候不需要花括号
import user from './user.js';
// 在命名冲突时,可以给导入的变量换个名字
import user as otherUser from './user.js';
// 将 export 组装成对象,包括 export default,对应 default 属性名
import * as obj from './user.js'
根据标准规范,导入模块时文件名必须包含 .js 后缀,因为 ESM 底层通过 URL 请求文件,URL 必须精确匹配。
实际开发中通常借助打包工具(如 webpack),通过配置 resolve.extensions 选项,在文件后缀缺失时自动补全后缀进行查找。
AMD
AMD 标准目前已过时,无需投入过多精力深入学习,仅需了解基本概念即可。
AMD 全称为 Asynchronous Module Definition(异步模块定义),是 ES Module 标准发布前,针对浏览器环境提出的一种异步模块加载方案。
由于 CommonJS 采用同步加载机制,而浏览器网络请求是异步的,因此 CommonJS 并不适合直接在浏览器端运行。
AMD 的核心特征是依赖前置,即所有依赖模块必须在函数开头明确指定。
实现 AMD 规范的典型库是 require.js,其基本用法如下。
模块导出的实现方式如下:
define(["./cart", "./inventory"], function (cart, inventory) {
// 从 cart.js 和 inventory.js 拿到对应的导出内容
// 然后通过返回值指定当前模块的导出内容
return {
color: "blue",
size: "large",
addToCart: function () {
inventory.decrement(this);
cart.add(this);
},
};
});需使用 define 函数,第一个参数指定依赖的其他模块,第二个参数为回调函数,可接收这些模块导出的内容,该函数的返回值即为当前模块的导出内容。
require 函数用于加载主模块,即程序入口文件。
require(["a", "b"], function(util) {
// a.js 和 b.js 加载完会执行这个函数
})CMD
CMD 标准同样已过时,且比 AMD 更为小众,了解其基本概念即可。
CMD 全称为 Common Module Definition,也是针对浏览器环境设计的模块化方案,与 AMD 属于同一时代产物。
与 AMD 不同,CMD 的特点是依赖就近,即在代码中实际用到某个模块时才引入,这种写法更接近 CommonJS。
代表库是 sea.js,其语法示例如下。
模块定义写法如下:
define(function(require, exports, module) {
// 导入 a 模块
var a = require('./a');
a.doSomething();
// 导出当前模块
module.exports = {
name: 'b',
start: function() {};
};
});主模块调用方式如下:
seajs.use(['./a', './b'], function(a, b) {
a.doSomething();
b.start();
});这种写法与 CommonJS 非常相似。
ES Modules 和 CommonJS 的区别
- CommonJS 模块在运行时加载,而 ESM 在编译时确定依赖关系;
- require 可在代码任意位置调用(如条件语句内),因为它是同步加载;import 必须位于模块顶层,不能置于其他作用域内,且 import 声明会提升;
- require 始终同步加载代码;import 通常也是同步的,但支持动态加载,此时表现为异步。例如
import('lang/zh.js').then(module) => { /* 获得语言包对象 */ }。 - CommonJS 导出的是值的拷贝,而 ESM 导出的是值的引用。例如 a.js 导出值为 1 的 count,b.js 导入该 count,若 a.js 中定时器将 count 修改为 2,在 b.js 中再次打印 count 时,CommonJS 下仍为 1,而在 ESM 下则变为 2(这表明编译时进行了特殊处理)。
- ESM 中 import 导入的变量是只读的,等价于使用 const 声明;CommonJS 则允许使用 var、let、const 任意关键字声明。
- CommonJS 支持导入 json 文件,而 ESM 原生不支持(实际开发中通过打包工具及转换器可实现各类文件导入);
UMD
面对众多模块标准,若需为不同系统构建不同文件则极为繁琐。若希望发布单一代码包,使其兼容多种模块系统,是否有可行方案?
可考虑使用 UMD(Universal Module Definition,通用模块定义),它能在 CommonJS 和 AMD 环境中运行,若均不匹配,则将模块暴露到全局环境。
以下是 webpack(版本 5.74.0)将 output.libraryTarget 配置为 "umd" 后的部分打包结果代码。
(function (root, factory) {
// 判断是否为 commonjs
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
// 判断是否为 requirejs
else if(typeof define === 'function' && define.amd)
define([], factory);
else {
var a = factory();
for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
}
})(self, () => {
// 模块内容
return {
user: '前端西瓜哥'
}
})其原理并不复杂:通过判断不同模块系统提供的全局变量是否存在且类型符合预期,来识别当前环境是 CommonJS 还是 require.js,进而使用对应语法。若均不匹配,则将模块暴露到全局变量中。
需要注意的是,UMD 无法支持 ES Modules,因为 import 是语言层面的关键字而非变量,在其他模块系统中使用会报错,且 import 只能在模块最外层使用。
结尾
模块系统繁多,给开发者带来一定心智负担,但 ES Modules 是正统标准,Node.js 现已完全支持。受历史遗留问题影响,我们仍需保留对 CommonJS 的支持。
本文介绍了 JavaScript 中五种模块系统的使用,更多相关内容可搜索本站往期文章或继续浏览后续文章,感谢支持。
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
