一:定义
- CommonJS (CJS)**:**非ECMA官方规范,用于NodeJS默认加载加载模块化方式,浏览器无法运行,使用**module.exports/require**导入模块文件。
- ESModule(ESM):ES6(ECMA)官网标准,浏览器 + Node通用模块化规范,前端工程主流方案,使用**export/import**导入模块文件。
二:使用场景
1. 模块的导入导出
|--------|---------------------------------------------------------|-------------------------------------------------------------|
| 规范 | 导出 | 导入 |
| CJS | module.exports = { a: 1, b: 2, ... } exports.a = 1 | const module = require("./xxx") |
| ESM | export const a = 1 export default = { a: 1, b: 2, ... } | import module from "./xxx" (静态、同步) import ("./xxx") (动态、异步) |
javascript
// CJS示例
// a.js
exports.num = 1
// index.js
const { num } = require('./a')
// ESM示例
// a.js
export let num = 1
// 静态导入(顶层专用)
import { num } from './a'
// 动态导入(任意位置,返回Promise)
import('./a').then(res=>{})
2. 加载顺序
# CJS:同步加载,会阻塞代码运行。require是普通函数,代码执行到当前行才会加载该文件,支持代码动态引入。
javascript
// CJS合法:if、for、函数内部任意位置写require
if(flag){
const m = require('./mod1')
}else{
const m = require('./mod2')
}
# ESM静态引入:编译时静态解析,提前写入全部模块路径,必须写在代码最顶层,代码同步加载。
javascript
// ESM 静态文件加载
// 默认 导入
import module from "./xxx"
// 命名 导入
import { module } from "./xxx"
// 全量导入
import * as module from "./xxx"
# ESM动态引入:可以运行时异步加载,返回Promise,import()是ES内置函数,CJS/ESM环境都能识别,任意位置都能调用,用于路由懒加载、按需引入。
javascript
// ESM 动态导入
import("./xxx")
// Promise
import("./xxx").then(res => {
// 业务代码
})
3. 导出值绑定:值拷贝 vs 实时绑定
# CJS :导出值的副本(浅拷贝),原模块变量变化不影响导入值
javascript
// c.js(CJS)
let count = 1
exports.count = count
exports.add = ()=>count++
// index.js(CJS)
const {count,add}=require('./c')
console.log(count) //1
add()
console.log(count) //1(拷贝快照,原变量变了不更新)
# ESM :导出实时引用绑定,原模块值变更,导入变量自动同步更新
javascript
// e.js(ESM)
export let count =1
export const add = ()=>count++
// index.js(ESM)
import {count,add} from './e'
console.log(count)//1
add()
console.log(count)//2(实时绑定,同步变化)
4. 静态优化能力(Tree-Shaking 关键)
# CJS :无法 Tree-Shaking,运行时动态解析依赖,打包阶段无法剔除未使用代码,打包体积偏大。
# ESM :支持 Tree-Shaking ,编译时确定导入导出,webpack/rollup 可删除未被引入的export代码,精简打包产物。
5. 顶层 this 与内置变量
# CJS:每个模块被包装成闭包函数,顶层 this = module.exports;
# ESM :顶层 this = undefined,默认开启严格模式use strict。
6. 模块缓存 & 循环依赖
# CJS :第一次require加载后缓存module.exports ,再次引入直接读缓存;循环依赖时返回未完成导出的空对象;
# ESM:通过拓扑排序解析依赖,循环依赖拿到实时引用,不会返回空对象。
7. 跨规范互导规则
ESM import CJS :只能整体默认导入,无法单独解构 CJS 命名导出。
javascript
// cjs模块:module.exports={a:1,b:2}
import all from './cjs' //✅正确,all={a:1,b:2}
import {a} from './cjs' //❌报错,ESM不能解构CJS导出
CJS require ESM :Node 原生不支持同步require(ESM),直接报ERR_REQUIRE_ESM,只能用await import()异步引入。
三:使用场景总结
# CJS:适用于Node 后端服务、脚手架工具、老项目遗留代码;
# ESM:适用于前端 Vue/React 项目、浏览器原生模块化、新项目标准选型。
四:小结
- CJS运行时同步加载、值拷贝、无TreeShaking;ESM编译时静态导入 + 动态import()异步、实时绑定、支持TreeShaking;
- CJS用require/module.exports,ESM用import/export。
- CJS环境不识别静态import,仅兼容import()动态导入。