
ESM(ECMAScript Module)不提供类似 CommonJS 中 module 对象的运行时模块作用域引用,任何试图在函数体内动态读写模块级变量(如 module.bar = 1)的做法均不可行;正确方式是显式声明模块级变量并合理导出/使用。
esm(ecmascript module)不提供类似 commonjs 中 `module` 对象的运行时模块作用域引用,任何试图在函数体内动态读写模块级变量(如 module.bar = 1)的做法均不可行;正确方式是显式声明模块级变量并合理导出/使用。
在 CommonJS 环境中,开发者习惯通过 module.exports 和 module 对象管理模块状态,甚至在函数内部动态挂载属性(如 module.bar = 1)来模拟“模块私有状态”。但在 ESM 中,模块作用域本质上是静态、封闭且不可反射的词法作用域——它不是可访问的对象,也没有对应的运行时句柄。module 变量在 ESM 中根本不存在,global 或函数自身(如 foo.bar = 1)也不能替代模块作用域语义。
❌ 常见误区与问题分析
module.bar = 1 → 语法错误
module 在 ESM 中未定义,直接访问会抛出 ReferenceError。-
foo.bar = 1(绑定到函数对象)→ 不可靠且易出错
虽然函数是对象,可附加属性,但这属于全局副作用:export async function foo() { foo.bar = 1; // ❌ 危险:并发调用时属性被覆盖;Tree-shaking 失效;破坏纯函数假设 }尤其在异步场景下,多次调用 foo() 会导致 foo.bar 被反复覆写,状态不可预测,且该模式违背模块封装原则。
globalThis.bar = 1 → 退化为全局污染
违反模块隔离性,丧失 ESM 的核心优势(作用域隔离、静态分析、依赖图明确),应严格避免。
✅ 推荐实践:显式声明 + 显式导出
ESM 鼓励声明式、可追踪、可维护的状态管理。若需模块级状态,请明确声明变量,并按需导出:
// math-utils.js
let bar = 1; // 模块级私有状态(仅本模块可修改)
export const getBar = () => bar;
export const setBar = (value) => { bar = value; };
export function foo() {
console.log('Current bar:', bar);
bar *= 2;
}
调用方清晰感知状态来源:
import { foo, getBar, setBar } from './math-utils.js';
foo(); // Current bar: 1 → bar becomes 2
console.log(getBar()); // 2
setBar(42);
⚠️ 注意事项总结
- 不要尝试“模拟” module 对象:ESM 的设计哲学是静态可分析性,动态挂载属性破坏这一前提;
- 避免函数对象属性存储状态:违反单一职责,导致隐式依赖和竞态风险;
- 慎用顶层 var/let/const:虽可实现模块状态,但需确保逻辑内聚,必要时封装为类或闭包;
- 异步安全优先:若状态涉及异步操作(如缓存、初始化),考虑使用 Promise 缓存或 AsyncLocalStorage(Node.js)等专用机制,而非裸变量。
简言之:ESM 中没有“模块作用域对象”,只有模块作用域本身——而它是通过 import/export 和顶层声明来定义和交互的。拥抱静态性,拒绝魔改,代码才更健壮、可读、可维护。










