Skip to content

CommonJS和ES Modules篇

CJS\AMD\CMD\UMD\ES Modules

1、认识

CommonJS(CJS)ES Modules(ESM) 是JS的两种模块化系统。在模块导入、导出、加载机制、使用场景等方面存在显著的区别。

🍎模块化发展历程

2009 => CommonJS 诞生,被Node.js作为一个标准开始制定使用,作为默认的模块化系统

2010年=>CommonJS 模块规范正式发布
2015 => ES6 Modules 标准化,同年也是ECMAScript 6(即 ES6)规范的发布
2020 => Node.js 13+ 正式支持 ES Modules
2023 => 90% 的新项目首选 ES Modules

🍎使用场景以及支持的环境

CommonJS

主要用于 Node.js 环境,虽然在浏览器中能通过工具(如 Webpack、Browserify)进行打包,但原生不支持。

ES Modules

浏览器和 Node.js 都支持 ES Modules,且现代浏览器普遍支持 <script type="module"> 标签。

🍎语法区别

CJS(CommonJS)

CommonJS 是一种同步加载模块的规范,主要用于服务器端的 JavaScript(比如 Node.js)。

plain
// 导入模块
const module = require('xxx');


//导出模块
module.exports = { xxx };

或者使用下面的写法

plain
exports.xxxFunction = function() { 
    xxx
};

ESM(ES Modules)

ES Modules是 JavaScript 官方标准(ECMAScript)的一部分,旨在在浏览器和服务器端(如 Node.js)中提供一致的模块化支持。

plain
// 导入模块 方式一 (推荐使用)
import { someFunction } from './module-name';

// 导入模块 方式二
import * as module from './xxx';
plain
// 导出模块 方式一(推荐使用)
export const someFunction = () => { };

// 导出模块 方式二
export default function() {  };

AMD (Asynchronous Module Definition)

2009年

AMD 是一种异步加载模块的规范,主要用于浏览器环境。它允许开发者在模块加载完成之前执行其他操作,从而提高页面加载速度。

在前端应运而生,对大型应用的加载性能优化有帮助

CMD (Common Module Definition)

2010年

由阿里巴巴的 SeaJS 提出,针对前端开发中的模块化问题,特别注重延迟加载和按需加载,与 AMD 类似,但更注重模块加载时依赖的延迟解析。

UMD (Universal Module Definition)

2011年

UMD 结合了 CommonJS 和 AMD 的特点,能够在浏览器和 Node.js 环境中同时使用。

2、CommonJS和ES Modules发展及解决问题

2009以前

JavaScript 初期,浏览器环境中的 JavaScript 程序通常是单一的、全局作用域的,所有代码都混合在一起。程序复杂以后,开发者的代码急切的需要一种模块化机制来组织代码、提高可重用性和可维护性

为了应对 Node.js 环境中对模块的需求,Node.js(2009年发布)使用了CommonJS作为默认的模块化系统,Node.js制定标准CommonJS,2010年CommonJS模块正式发布。

CommonJS 模块是同步加载的,使得它适用于服务器端应用。

但CommonJS 模块是在运行时加载的,导致无法进行静态分析,像 Tree Shaking 和 代码分割(Code Splitting) 这样的优化无法在打包时进行

2015年ECMAScript 6(即 ES6)规范发布,内建模块系统ES Modules(ESM),ES6 Modules 彻底标准化。ESM 的静态结构使得 JavaScript 引擎能够在编译时进行优化,Tree Shaking 和 代码分割(Code Splitting)信手拈来,并且**跨平台支持。**2020年Node.js 13+ 全力支持 ES Modules,ESM成为主流。

3、CommonJS和ESM的区别和不同

🍎加载方式的区别

CommonJS同步加载,当执行 require() 时,Node.js 会立即加载并执行模块代码。加载后的模块被缓存,并且返回模块的导出对象。适合于服务器端的同步需求。

CommonJS不能在浏览器中直接使用(直到现代浏览器支持 ES Modules)

ES Modules是异步加载 的,适用于浏览器和服务器端

支持静态分析,编译器和打包工具可以提前分析模块的依赖关系

支持动态导入,使用 import() 函数异步加载模块

涉及到按需加载和优化性能时非常好用

🍎模块的解析和执行时机

CommonJS模块代码立即执行,当 require() 被调用时,模块会立即被加载并执行一次

ESModules模块代码按需加载,模块的导入是静态绑定,导入的值是模块首次执行时的值,且不再变化

🍎默认导出

CommonJS使用 module.exports 进行导出,默认导出的模块对象不需要特别标注

plain
// CommonJS 导出
module.exports = function() { /* ... */ };

ES Modules支持 default 导出,直接导出一个默认值

plain
// ES Modules 导出
export default function() { /* ... */ };

🍎模块缓存

CommonJS在第一次被 require() 时会被加载并缓存,后续的 require() 调用将直接返回缓存的模块对象

ES Modules也会被缓存,但每个模块导入时,它是只读的,按照静态结构导出,不会改变

🍎在 Node.js的支持

CommonJS

Node.js 原生支持 CommonJS,从 Node.js 的最早版本开始就被广泛使用

ES Modules

🛹v12.x 开始初步实验性支持

Node.js开始引入对 ES Modules 的支持

🛹Node.js14实验性支持阶段

ESM逐渐稳定和广泛,引入ESM的实验性支持

使用.mjs文件扩展名或通过"type": "module"配置项启用对ESM的支持,启用ESM模块支持

🛹Node.js 16.x

ESM 成为稳定功能,支持 .js 文件和 .mjs 文件,项目中可以全局启用

🛹Node.js 18.x和20.x

增强ESM 支持,进一步优化与 CJS 的互操作性

🛹 未来

ESM是大势所趋

🍎循环依赖处理

CommonJS

CommonJS 处理循环依赖时,返回的是已加载的模块的部分内容,这可能导致循环依赖的一些问题。

ES Modules

ESM 通过 "死锁" 模式来处理循环依赖,确保模块的内容始终是稳定和一致的。

4、两者对比

特性CommonJSESModule (ESM)
加载方式同步加载异步加载(支持动态导入)
支持懒加载
模块语法require()
module.exports
import
export
跨平台兼容性主要适用于 Node.js,浏览器需使用工具支持浏览器和 Node.js 都支持
导出类型无法区分默认导出与命名导出支持默认导出
export default
模块缓存会缓存已加载模块缓存且模块是只读的
循环依赖处理可能会返回部分模块更好地处理循环依赖
适用服务器端的同步加载和模块化现代的前端开发和浏览器环境
优点兼容性良好、同步加载、简单易用支持异步加载、静态分析和更清晰的模块结构
静态分析和优化无法进行静态分析,不能进行优化支持静态分析
支持Tree Shaking(去除未使用的代码)
代码分割(Code Splitting)
性能较为适合服务器端,浏览器中性能较差支持懒加载和优化,适用于浏览器和 Node.js
生态系统已有庞大的生态系统,几乎所有 Node.js 包都使用生态系统逐步成长,尤其是在现代 JavaScript 项目中
互操作性与其他模块系统兼容良好与 CommonJS 存在兼容性问题
支持的 Node.js 版本所有 Node.js 版本均支持Node.js 从 v12 开始全面支持 ESM
错误处理与模块执行顺序顺序执行,无异步问题模块执行有异步加载的潜在影响
学习曲线简单,易于上手对于老旧项目的迁移可能需要更多工作
顶层await支持不支持支持

5、衍生

CJS\AMD\CMD\UMD\ES Modules

Released under the MIT License.