es模块作用域通过引擎强制的物理级隔离实现全局变量污染防护,其顶层声明仅绑定于专属模块环境记录,与全局环境完全分离,且不可被globalthis访问或枚举。

模块作用域对全局变量污染的隔离不是“逻辑约定”,而是由 JavaScript 引擎在加载和执行阶段实施的物理级约束——它不依赖开发者自觉,也不靠运行时检查,而是通过语言规范强制定义的执行上下文结构。
模块被自动封装进独立的环境记录(Environment Record)
每个 ES 模块文件(如 utils.js)在解析时,引擎不会将其顶层代码当作全局脚本执行,而是直接绑定到一个专属的模块环境记录(Module Environment Record)。这个记录是引擎内部的数据结构,与全局环境记录(Global Environment Record)完全分离:
- 顶层声明的 const、let、function 只注册进该模块的环境记录,不会写入
window或globalThis - 即使使用 var,也不会触发变量提升到全局,而是绑定到模块环境记录的“声明性绑定”中
-
this在模块顶层始终为undefined(严格模式默认启用),杜绝了this.x = 1意外挂载到全局对象
模块加载即触发静态作用域切分,无运行时包装开销
不同于 CommonJS 需要运行时用函数包裹((function(exports, require, ...){...})),ES 模块的隔离发生在语法解析阶段:
- 引擎读取
<script type="module"></script>或import语句时,立即为该模块分配独立的词法环境 - 所有 import 绑定在编译期就建立静态引用关系,不经过全局对象中转
- export 不是“把变量塞进 window”,而是向模块记录表注册一个可被其他模块按名查取的绑定入口
全局对象彻底失权:无法被动接收、无法反射遍历
在非模块脚本中,var a = 1 等价于 window.a = 1;但在模块中,这种隐式赋值路径被切断:
-
globalThis.hasOwnProperty('x')对模块顶层声明的x返回false -
for (const key in globalThis)列不出任何模块顶层变量 - 即便用
eval('x = 2')(非严格模式下),也无法在模块内创建全局属性——因为 eval 执行上下文仍属于当前模块环境
与非模块脚本的对比暴露物理差异
同一页面中并存两种脚本,行为截然不同:
-
<script>var a = 1; let b = 2;</script>→globalThis.a === 1成立,b虽不可枚举但仍在全局作用域可读 -
<script type="module">var a = 1; let b = 2;</script>→globalThis.a和globalThis.b均为undefined,且a和b在模块外完全不可访问











