es模块导出的是只读绑定而非只读值,允许修改引用类型内容但禁止重新赋值导入变量;commonjs导出的是可变对象引用,无只读限制但无响应式更新。

JS 模块导出的变量本身不是“只读值”,而是**只读绑定(read-only binding)**——关键区别在于:你不能在导入方重新赋值这个变量名,但可以修改它指向的对象内容(如果它是引用类型)。
ES Module 中的“只读绑定”怎么理解
ESM 的 import 语句创建的是对导出变量的**实时、只读绑定**,不是拷贝,也不是普通 const 声明那种值保护。
- ✅ 允许操作:修改对象属性、数组元素、Map/Set 内容等(因为操作的是同一内存地址)
- ❌ 禁止操作:用
=给导入的变量重新赋值(会报错Assignment to constant variable) - ⚠️ 注意:如果导出方自己执行了重新赋值(如
list = []),导入方的绑定会自动更新指向新值(活绑定特性)
CommonJS 导出的是“可变对象引用”
CommonJS 使用 module.exports,导出的是一个对象的浅拷贝引用,导入后拿到的是该对象本身(不是绑定)。
- 你可以自由修改这个对象的属性(
math.PI = 4),其他 require 地方也会看到变化 - 但如果你在导出模块里重新给
module.exports赋值(比如改成函数),已导入的地方不会更新——因为 require 返回的是首次执行时的快照 - 没有“只读绑定”机制,所以不存在 import 后禁止赋值的问题,但也没有响应式更新
为什么导出基础类型(string/number/bool)看起来像“只读”
原始值(primitive)是按值传递的,ESM 导出原始值时,其实导出的是对它的“只读绑定”。但由于原始值不可变,你既不能改它的值,也不能重新赋值导入变量,所以表现上完全不可变。
-
export const VERSION = "1.0"→ 导入后VERSION = "2.0"报错 - 即使导出模块内部 later 改成
VERSION = "2.0",ESM 也不允许(const 声明 + 只读绑定双重限制) - 真正能“变”的,只有可变引用类型(对象、数组、类实例)的内容操作
安全导出变量的实践建议
避免直接导出裸露的可变变量(如 export let items = []),容易引发意外修改或同步混乱。
- 优先封装状态:用函数返回、getter/setter 或 class 控制访问
- 导出不可变结构:如
export const config = Object.freeze({...}) - 导出操作方法而非数据本身:如
export function addItem(x) { ... },而不是导出list - 需要共享状态时,明确用单例对象或事件总线,而非依赖模块变量的“隐式共享”











