ES6模块与CommonJS本质不同:前者静态、编译期确定依赖,后者动态、运行时加载;混用会导致ReferenceError、undefined导出等问题,需依场景选择并正确配置。

ES6 模块(import/export)和 CommonJS(require/module.exports)不是“换种写法就行”的关系——它们在加载时机、导出行为、循环引用处理、运行时表现上根本不同。直接混用或盲目替换会触发 ReferenceError、undefined 导出、甚至打包器报错。
ES6 模块是静态的,CommonJS 是动态的
这是最核心差异。ES6 的 import 必须在模块顶层,不能放在 if 或函数里;而 require 可以随时调用,支持条件加载、拼接路径等运行时逻辑。
常见错误现象:
-
import写在if块里 → 语法错误:Cannot use import statement outside a module - 用
import { foo } from './utils.js'但utils.js实际是 CommonJS 格式(无export)→ Node.js 报Cannot access 'foo' before initialization或导入为undefined
实操建议:
立即学习“Java免费学习笔记(深入)”;
- Node.js 中启用 ES6 模块需在
package.json加"type": "module",否则默认按 CommonJS 解析 - 若想在 CommonJS 文件中导入 ES6 模块,必须用
await import('./es-module.js')(动态导入),不能用require - Webpack/Vite 等工具能自动处理互转,但底层仍依赖静态分析 —— 所以
export不能写在条件分支里
export default 和 module.exports 不是一对一映射
ES6 的 export default 导出的是一个值的**绑定**(binding),CommonJS 的 module.exports 是一个可随意赋值的对象引用。这意味着:
- ES6 中修改
export default对应的变量,其他模块看到的值会实时更新(如计数器、状态对象) - CommonJS 中一旦执行
module.exports = xxx,后续再改xxx不会影响已导出的值
示例对比:
// esm.js let count = 0; export default () => ++count; // cjs.js let count = 0; module.exports = () => ++count;
如果两个文件都被多次 import / require,ES6 版本共享同一份 count;CommonJS 版本每次 require 都新建闭包,count 各自独立。
循环引用时,ES6 返回未初始化的绑定,CommonJS 返回已执行的 exports 对象
这是最容易踩坑的场景。比如 A 导入 B,B 又导入 A:
- CommonJS:A 执行到
require('./b')时,B 已导出空exports对象,A 拿到它继续执行;B 后续给exports.xxx赋值,A 能读到 - ES6:A 的
import { x } from './b'在解析阶段就建立绑定,但此时 B 还没执行完,x是undefined;即使 B 后面export const x = 1,A 中的x仍为undefined(除非用export default或命名导出后立即赋值)
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 避免循环引用 —— 提取公共逻辑到第三个模块
- 若必须循环,ES6 下优先用
export default(它不依赖命名绑定顺序),或确保导出语句在模块顶部且无依赖 - Node.js 的
require缓存机制会让循环引用“看起来工作”,但这不是可靠设计依据
浏览器中只能用 ES6 模块,Node.js 默认只认 CommonJS
浏览器原生只支持 type="module" 的 ,不理解 require;Node.js v12+ 支持 ES6 模块,但需显式声明(.mjs 后缀或 "type": "module"),否则一律当 CommonJS 处理。
常见错误现象:
- 在 HTML 中写
,但index.js用了import→ 控制台报Uncaught SyntaxError: Cannot use import statement outside a module - Node.js 中
index.js有import却没设"type": "module"→SyntaxError: Cannot use import statement outside a module
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 浏览器端:用
,路径必须是相对/绝对 URL(不能省略.js) - Node.js:要么统一用
.mjs后缀,要么在package.json顶层加"type": "module";混合项目可用createRequire兼容旧代码 - 跨环境库(如工具函数包)建议同时提供 ESM 和 CJS 构建产物,并在
package.json中用"exports"字段精确控制入口
真正难的不是语法转换,而是理解两种模块系统背后的设计哲学:一个是编译期确定依赖图、强调不可变绑定与静态分析;另一个是运行时灵活加载、强调可变状态与动态能力。很多“兼容问题”其实源于强行把一种范式套进另一种约束里。











