esmodule导入是实时只读引用,修改导出端立即生效但不可在导入侧赋值;commonjs导入是执行时浅拷贝,后续导出变化不影响已导入值。

ESModule 的导入变量是只读的“活绑定”(live binding),而 CommonJS 是导入时对值的一次性浅拷贝。这个区别直接影响模块间数据同步和赋值行为,不是语法习惯问题,而是运行时机制差异。
ESModule:导入的是导出值的实时只读引用
ESM 中 import 声明绑定的是原始模块中 export 的变量本身,而非副本。只要原模块重新赋值(且是可变的顶层绑定),所有导入方都能看到更新——但你不能在导入侧重新赋值该变量,否则会报错 TypeError: Cannot assign to import。
- 导出变量必须是顶层声明(
let/const/var或函数/类),ESM 会建立“绑定关系” - 即使导出的是基本类型(如
export let count = 0),导入后仍能响应count++(在原模块中执行) -
注意:只读限制仅作用于导入语句绑定的标识符(如
import { x } from './a.js'中的x),不阻止修改对象属性或数组元素
CommonJS:导入的是值的浅拷贝(执行时快照)
require() 返回的是模块 exports 对象的副本(更准确说是对其当前状态的引用拷贝)。后续原模块修改 exports.xxx 或重新赋值 module.exports,不会影响已 require 的结果。
- 模块加载时执行整个文件,把
module.exports当前值复制给调用方,之后两者完全解耦 - 若导出一个对象:
module.exports = { a: 1 },导入后修改obj.a = 2会影响原对象(因为是同一引用),但module.exports = { a: 3 }后,老 require 结果不变 - 无法实现跨模块响应式更新,典型场景如共享配置对象需手动监听或重 require
关键对比示例
假设模块 counter.js:
// counter.js
export let value = 42;
export function inc() { value++ }
ESM 导入侧:
import { value, inc } from './counter.js';
console.log(value); // 42
inc();
console.log(value); // 43 ← 实时更新
value = 99; // ❌ TypeError!不允许重新赋值导入绑定
CommonJS 版本:
// counter.cjs
let value = 42;
module.exports = {
get value() { return value; },
inc() { value++ }
};
const { value, inc } = require('./counter.cjs');
console.log(value); // 42
inc();
console.log(value); // 42 ← 仍是旧值,getters 不自动刷新
为什么设计成这样?
ESM 的只读活绑定支持静态分析、循环依赖安全处理、Tree-shaking 等构建优化;CommonJS 的即时求值与拷贝模型更贴近传统脚本执行逻辑,也更易与动态 require 场景兼容。两者没有绝对优劣,但迁移时务必注意状态共享逻辑是否被破坏。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











