
在 ES 模块(ESM)中,模块作用域并非一个可被直接引用或赋值的对象(如 CommonJS 中的 module),因此无法通过 module.bar = 1 等方式动态挂载属性;正确做法是显式声明变量并导出,以保障代码的可维护性、可预测性与静态分析兼容性。
在 es 模块(esm)中,模块作用域并非一个可被直接引用或赋值的对象(如 commonjs 中的 `module`),因此无法通过 `module.bar = 1` 等方式动态挂载属性;正确做法是显式声明变量并导出,以保障代码的可维护性、可预测性与静态分析兼容性。
ESM 的设计哲学强调静态可分析性和显式依赖。与 CommonJS 中可通过 module.exports 或 module 对象动态扩展模块状态不同,ESM 的导出必须在顶层(top-level)静态声明——即不能在函数体、条件分支或异步回调中动态添加导出成员。
例如,以下写法在 ESM 中非法且不可行:
export function foo() {
// ❌ 错误:module 在 ESM 中不存在,此代码会抛出 ReferenceError
module.bar = 1;
}同样,试图通过函数自身属性模拟模块状态也存在严重缺陷:
export async function foo() {
foo.bar = 1; // ⚠️ 危险:非线程安全,多次调用将覆盖/竞争;无法被静态分析识别为模块导出
}该方式不仅破坏了模块的纯静态结构(导致 tree-shaking 失效、类型推导中断、IDE 自动补全失效),更在并发或重入场景下引发状态污染问题(尤其在 async 函数中,多个调用共享同一函数引用,foo.bar 成为隐式全局状态)。
✅ 正确实践:使用顶层声明 + 显式导出
// ✅ 推荐:清晰、静态、可预测
let bar = 1;
export function foo() {
return bar;
}
export function setBar(value) {
bar = value;
}
// 可选择导出当前值(只读视图)
export const currentBar = () => bar;若需封装私有状态并提供受控访问,可结合闭包与命名导出:
// utils.js
const state = { bar: 1 };
export function foo() {
return state.bar;
}
export function updateBar(value) {
state.bar = Number(value);
}
// 导出只读副本(避免外部篡改)
export const config = Object.freeze({ get bar() { return state.bar; } });? 注意事项总结:
- 不要尝试模拟 module 对象:ESM 规范未暴露模块作用域的运行时对象,任何“取巧”方案(如 this、import.meta、函数自引用)均属非标准、不可靠且易出错;
- 避免副作用驱动的状态管理:模块级状态应通过明确声明与导出控制,而非隐式挂载;
- 优先使用 const / let 声明 + export 组合:既满足封装需求,又完全兼容工具链(TypeScript、Vite、Webpack、ESLint 等);
- 异步场景需额外谨慎:若状态需在异步流程中更新,请配合 Promise 链、async/await 显式控制时序,而非依赖函数属性。
归根结底,ESM 的约束不是限制,而是引导开发者回归意图明确、边界清晰、易于协作的模块化正途。放弃对动态模块对象的执念,拥抱静态导出,才能真正发挥现代 JavaScript 模块系统的全部优势。

















