CommonJS 和 ES Modules 的区别是什么?
核心区别
| 特性 | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| 语法 | require/module.exports |
import/export |
| 加载方式 | 运行时动态加载 | 编译时静态分析 |
| 输出 | 值的拷贝 | 值的引用 |
| this | this === module.exports |
this === undefined |
| 循环依赖 | 返回已执行部分 | 引用绑定 |
| 顶层 this | module.exports | undefined |
| 动态导入 | require() |
import() |
| 适用 | Node.js 服务端 | 浏览器 + Node.js |
值拷贝 vs 值引用
CommonJS — 值拷贝:
// lib.js
let count = 0
function increment() { count++ }
module.exports = { count, increment }
// app.js
const { count, increment } = require('./lib')
increment()
console.log(count) // 0(拷贝,不受影响)ESM — 值引用:
// lib.mjs
export let count = 0
export function increment() { count++ }
// app.mjs
import { count, increment } from './lib.mjs'
increment()
console.log(count) // 1(引用,同步变化)动态加载
// CJS:require 是同步的
const lib = condition ? require('./a') : require('./b')
// ESM:import 是静态的,但可以用 import() 动态加载
const lib = await import(condition ? './a.mjs' : './b.mjs')导出方式
// CJS
module.exports = { add, sub } // 对象导出
module.exports.add = add // 单个导出
module.exports = function() {} // 函数导出
// ESM
export { add, sub } // 命名导出
export default function() {} // 默认导出
export const PI = 3.14 // 内联导出导入方式
// CJS
const { add } = require('./math') // 解构导入
const math = require('./math') // 整体导入
// ESM
import { add } from './math.mjs' // 命名导入
import math from './math.mjs' // 默认导入
import * as math from './math.mjs' // 命名空间导入
import './polyfill.mjs' // 副作用导入Node.js 中的模块识别
// package.json
{
"type": "module" // .js 文件作为 ESM 处理
// 或 "type": "commonjs" // 默认
}
// 文件扩展名
.js → 取决于 package.json type
.mjs → 始终 ESM
.cjs → 始终 CommonJS互操作
// ESM 中导入 CJS
import pkg from 'commonjs-package' // pkg === module.exports
// CJS 中导入 ESM(异步)
const esm = await import('./esm-module.mjs')Tree Shaking
- CJS 不支持:require 是动态的,无法静态分析
- ESM 支持:import/export 是静态的,打包工具可以分析依赖关系
最佳实践
- 新项目一律使用 ESM
- 发布库时提供 ESM 和 CJS 两种入口
- Node.js 用
.mjs或"type": "module"