ESM导出项是只读活绑定,不可赋值修改;其本质是动态引用而非值拷贝,保障状态可控、支持静态分析与循环依赖安全,需通过封装方法实现可变逻辑。

ESM 模块的导出项本质上是只读绑定(live read-only bindings),不是普通变量的拷贝或快照。这意味着外部模块可以读取它、使用它,但无法通过赋值操作(=)修改它的值——哪怕它导出的是一个对象或函数,其绑定本身仍不可重绑定。
导出绑定是动态的、实时的引用
ESM 的 export 声明创建的是对内部声明的活引用(live binding),而非值拷贝。例如:
/* math.js */
let count = 0;
export { count };
export const add = () => ++count;当另一个模块导入并尝试重写:
/* main.js */
import { count } from './math.js';
count = 999; // ❌ TypeError: Assignment to constant variable.即使 count 是 let 声明,导出后在导入方也表现为只读绑定。这是模块系统强制的语义,目的是保障模块状态的可控性和可预测性。
为什么不允许外部重写?三个关键原因
- 防止意外副作用:若允许任意模块修改其他模块的导出值,调用链中任一环节都可能静默改变共享状态,调试难度陡增
- 支持静态分析与优化:构建工具(如 Vite、Webpack)依赖“导出不可变”这一前提做 tree-shaking 和依赖追踪;若导出可被覆盖,静态推断将失效
- 避免循环初始化冲突:在存在导入依赖环(如 A → B → C → A)时,只读绑定能确保各模块在初始化完成前不被非法篡改,维持加载时序安全
想改变导出行为?正确做法是封装可变逻辑
如果需要对外提供可更新的状态,应显式暴露方法而非裸露变量:
/* config.js */
let _timeout = 5000;
export const getTimeout = () => _timeout;
export const setTimeout = (ms) => { _timeout = ms; };
export const resetTimeout = () => { _timeout = 5000; };这样既保持了导出项的不可重写性,又把变更控制收归模块内部,符合封装原则和单向数据流设计思想——数据只能由定义它的模块决定如何产生与更新。

















