CommonJS和ES6 Modules区别

发布于:2025-08-03 ⋅ 阅读:(12) ⋅ 点赞:(0)

CommonJS 和 ES Modules(ESM)的区别

CommonJSES Modules (ESM) 是 JavaScript 中两种主流的模块规范,分别用于不同场景(Node.js 与浏览器/现代 Node.js),核心区别体现在语法、加载机制、运行时行为等方面。

1. 语法差异(最直观的区别)

CommonJS
  • 导出:使用 module.exportsexports
  • 导入:使用 require()
// 导出(math.js)
function add(a, b) { return a + b; }
module.exports = { add };  // 整体导出
// 或 exports.add = add;  // 单个导出

// 导入(index.js)
const math = require('./math.js');
console.log(math.add(1, 2)); // 3
ES Modules (ESM)
  • 导出:使用 export(命名导出)或 export default(默认导出)
  • 导入:使用 import
// 导出(math.js)
export function add(a, b) { return a + b; }  // 命名导出
// 或 export default { add };  // 默认导出

// 导入(index.js)
import { add } from './math.js';  // 对应命名导出
// 或 import math from './math.js';  // 对应默认导出
console.log(add(1, 2)); // 3

2. 加载机制:运行时加载 vs 编译时加载

CommonJS:运行时动态加载
  • 模块加载发生在代码执行阶段(运行时),require() 是一个函数,执行时才会加载模块。
  • 支持动态导入(可在条件语句中加载模块):
  • CommonJs是动态语法可以写在判断里
// 合法:根据条件动态加载
if (condition) {
  const math = require('./math.js');
}
  • 加载的是模块的拷贝(值传递):导入后,模块内部的后续修改不会影响导入结果。
ESM:编译时静态加载
  • 模块加载发生在代码解析阶段(编译时),import 声明必须放在文件顶部,且不能在条件语句中使用(静态分析)。
  • ES6 Module静态语法只能写在顶层,不支持直接在条件语句中写 import,但可通过 import() 函数实现动态加载(返回 Promise):
// 合法:动态导入(ES2020 新增)
if (condition) {
  import('./math.js').then(({ add }) => console.log(add(1, 2)));
}
  • 加载的是模块的引用(引用传递):导入后,模块内部的修改会实时反映到导入处。

3. 模块依赖:动态依赖 vs 静态依赖

  • CommonJS:依赖关系在运行时确定,模块路径可动态生成(如拼接字符串):

CommonJS 是运行时解析
CommonJS 的模块加载是动态的,require()是一个运行时执行的函数,可以写在代码的任何位置(如条件语句、循环中)。模块的依赖关系只有在代码执行到require()语句时才会被解析和加载,无法在编译阶段(代码执行前)确定完整的依赖树。

const path = './math.js';
const math = require(path);  // 合法
  • ESM:依赖关系在编译时确定,模块路径必须是静态字符串(不能动态生成):

ES Modules 是编译时解析
ES Modules 的 import 声明是静态的,必须写在模块的顶层(不能嵌套在条件语句中)。浏览器或 JS 引擎会在代码执行前(编译阶段)就解析所有 import 声明,构建完整的模块依赖关系图,这种静态分析能力让 ESM 支持树摇(tree-shaking)、类型检查等编译时优化。

const path = './math.js';
import { add } from path;  // 报错!路径必须是静态的

4. 循环依赖处理

循环依赖指 A 依赖 B,B 同时依赖 A 的情况,两者处理方式不同:
CommonJS
  • 当模块循环引用时,require() 会返回已执行部分的 exports(可能不完整)。
  • 例:
// a.js
const b = require('./b.js');
console.log('a 中 b 的值:', b);  // { b: undefined, getB: [Function] }
module.exports = { a: 1 };

// b.js
const a = require('./a.js');
console.log('b 中 a 的值:', a);  // {}(此时 a 尚未导出完成)
module.exports = { 
  b: 2,
  getB: () => a.a  // 后续可通过函数获取完整值
};
ESM
  • 循环依赖时,模块会通过引用绑定保持关联,即使依赖未完全加载,也能获取到最终值。
  • 例:
// a.js
import { b, getB } from './b.js';
console.log('a 中 b 的值:', b);  // undefined(初始值)
export const a = 1;
console.log('a 中 getB():', getB());  // 1(获取到 a 的最终值)

// b.js
import { a } from './a.js';
export let b = 2;
export const getB = () => a;  // 引用 a 的绑定,最终会获取到 1

5. 环境支持

  • CommonJS

    • 主要用于 Node.js(默认模块系统),浏览器不原生支持(需通过 Webpack 等工具转译)。
    • 模块文件扩展名可省略(Node.js 会自动补全 .js.json 等)。
  • ESM
    • 浏览器原生支持(需在 <script type="module"> 中使用)。
    • Node.js 从 v12 开始支持(需将文件扩展名改为 .mjs,或在 package.json 中设置 "type": "module")。

6. 顶层作用域

● CommonJS:
每个模块的顶层 this 指向 module.exports,且存在 module、exports、require 等内置变量。
● ES Modules:
顶层 this 为 undefined,没有 module、exports 等变量,只能通过 import/export 操作模块。

7. 加载方式

CommonJS:
○ 同步加载:模块加载会阻塞后续代码执行,适合服务器端(Node.js),因为文件存在于本地磁盘,加载速度快。
ES Modules:
○ 异步加载:模块加载不会阻塞代码执行,适合浏览器环境(网络请求可能延迟),且支持并行加载多个模块。

总结
● CommonJS 是 Node.js 的传统模块规范,适合服务器端,支持动态加载,但不支持 Tree-shaking。
● ESM 是 ES 标准模块规范,浏览器和现代 Node.js 均支持,静态加载支持 Tree-shaking,更适合前端工程化。


网站公告

今日签到

点亮在社区的每一天
去签到