js中common.js和ECMAScript.js区别
以下是关于 CommonJS 和 ECMAScript Modules(ESM)的详细对比分析,包含底层原理和示例说明:
🧩 核心差异对比表
特性 | CommonJS | ES Modules |
---|---|---|
来源 | Node.js 社区规范 | ECMAScript 语言标准 |
加载方式 | 动态加载(运行时解析) | 静态加载(编译时解析) |
加载环境 | Node.js 原生支持 | 浏览器原生支持,Node.js需开启 --experimental-modules (v13.2+已稳定) |
语法格式 | require() / module.exports | import / export |
加载行为 | 同步加载 | 异步加载 |
模块解析 | 文件路径需完整 | 支持 bare module 说明符(需要导入映射) |
变量访问 | 修改原始导出对象 | 绑定只读引用 |
循环引用处理 | 部分加载(未完成的状态) | 引用预解析(存在TDZ) |
顶层作用域 | 模块内this 指向module.exports | 顶层this 为undefined |
静态分析 | 不支持 Tree-shaking | 支持 Tree-shaking 优化 |
📦 底层加载机制差异(图示)
CommonJS 运行时解析流程
1. 执行代码 → 2. 构建模块对象 → 3. 按需加载依赖 → 4. 包裹成函数执行
Module Wrapper 伪代码:
function (exports, require, module, __filename, __dirname) {// 用户代码在此执行module.exports = ...;
}
ESM 预解析流程
1. 解析阶段 → 2. 建立模块关系图 → 3. 编译阶段 → 4. 实例化 → 5. 执行代码
关键特性:
- 模块记录(Module Record):存储导入/导出关系
- 实时绑定(Live Bindings):导出值变化会同步到导入方
🛠️ 代码示例对比
模块导出差异
// CommonJS 动态修改
exports.a = 1; // ⇨ { a: 1 }
module.exports = { b: 2 }; // 最终导出 { b: 2 }// ESM 绑定不可变
export let count = 0;
export function increment() {count++; // 所有导入模块都会看到更新后的值
}
循环依赖处理
// commonjs/a.js
console.log('a开始');
exports.done = false;
const b = require('./b'); // 此时b尚未完成加载
console.log('在a中,b.done =', b.done);
exports.done = true;
console.log('a结束');// commonjs/b.js
console.log('b开始');
exports.done = false;
const a = require('./a'); // 此时a导出{done: false}
console.log('在b中,a.done =', a.done);
exports.done = true;
console.log('b结束');
# 执行结果:
a开始 → b开始 → 在b中,a.done = false → b结束 → 在a中,b.done = true → a结束
⚡ 现代项目中的互操作性
混合使用解决方案
// 在 ESM 中引入 CJS
import cjsModule from './commonjs-module.cjs';// 在 CJS 中引入 ESM(需异步)
const esModule = await import('./es-module.mjs');
Package.json 配置
{"type": "module", // 默认使用ESM"main": "./index.cjs", // CJS入口"exports": {"import": "./esm/index.js", // ESM入口"require": "./cjs/index.js" // CJS入口}
}
🔧 转译工具处理原理(以Babel为例)
# 转换步骤示例
ESM → 解析为AST → 检测import/export → 替换为require语法 → 添加helper函数
示例转换效果:
// 原始ESM
import { readFile } from 'fs';
export const data = readFile('./file.txt');// 转换后CommonJS
const { readFile } = require('fs');
exports.data = readFile('./file.txt');
🚀 性能优化差异
-
CommonJS 优化难点
- 无法预知依赖关系,阻碍并行加载
- 动态表达式导致死代码难以消除
require(condition ? 'a' : 'b'); // 无法静态分析
-
ESM 优化空间
// webpack利用静态分析实现的特性 import(/* webpackPrefetch: true */ './chart'); // 预取 import(/* webpackChunkName: "utils" */ './utils'); // 分块命名
🌐 浏览器支持情况
浏览器 | ESM支持版本 |
---|---|
Chrome | 61+ |
Firefox | 60+ |
Safari | 10.1+ |
Edge | 16+ |
<!-- 浏览器直接使用ESM -->
<script type="module" src="app.js"></script>
💡 选用建议
-
Node.js 服务端
- 新项目 > Node 14:优先使用ESM
- 旧项目迁移:逐步替换关键模块
-
前端工程
- 统一使用ESM(配合webpack等打包工具)
- 第三方库需提供ESM版本(通过
package.json
的module
字段)
-
工具库开发
# 推荐双模式发布 lib/ ├── esm/ # ESM版本(支持Tree-shaking) ├── cjs/ # CommonJS版本 └── index.d.ts # 类型声明
两种模块系统在JavaScript生态中仍将长期共存,理解其底层机制有助于更高效地处理模块化问题。随着Node.js对ESM支持的完善,未来ESM会成为主流选择,但CommonJS仍将在老项目中持续存在。