CommonJS和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">
标签。
🍎语法区别
CommonJS
CommonJS 是一种同步加载模块的规范,主要用于服务器端的 JavaScript(比如 Node.js)。
plain
// 导入模块
const module = require('xxx');
//导出模块
module.exports = { xxx };
或者使用下面的写法
plain
exports.xxxFunction = function() {
xxx
};
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() { };
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
在 Node.js 中,从 v12.x 开始引入对 ES Modules 的支持,但需要使用 .mjs
文件扩展名或通过 "type": "module"
配置项启用对 ESM 的支持。
到 Node.js 14 版本,ESM 才逐渐变得更加稳定和广泛使用。
🍎循环依赖处理
CommonJS
CommonJS 处理循环依赖时,返回的是已加载的模块的部分内容,这可能导致循环依赖的一些问题。
ES Modules
ESM 通过 "死锁" 模式来处理循环依赖,确保模块的内容始终是稳定和一致的。
4、两者对比
特性 | CommonJS | ESModule (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** 支持 |
不支持 | 支持 |