ESM模块本身不污染全局,但显式赋值window、非模块脚本、旧库及第三方SDK仍会导致全局污染;可通过Object.keys(window)等方法快速检测。
在 es modules(esm)时代,模块脚本默认隔离作用域,顶层声明的 const、let、function 不会自动挂到 window 上——但这不等于全局污染风险消失了。真正要警惕的,是那些“主动打破隔离”的行为,以及混合开发场景中隐蔽的 window 绑定路径。
模块内显式赋值 window 是最直接的污染信号
ESM 本身不污染,但开发者可以手动污染。只要看到类似下面的代码,就说明正在向全局泄漏:
-
window.api = fetchApi;或globalThis.utils = {...}; -
window.addEventListener('load', init);中的init是未声明的函数名(隐式全局) - 模块里用
var声明后又在非严格上下文里执行eval('x = 1')(虽罕见,但可能)
这类赋值不会报错,却让变量出现在 for (key in window) 可遍历范围,且与其他脚本共享命名空间。
非模块脚本与模块共存时,污染源往往来自“另一半”
一个页面常同时存在 <script> 和 <script type="module">。此时:
- 普通
<script>中的var a = 1仍等价于window.a = 1,而模块脚本完全感知不到这个a - 若某旧库(如 jQuery 插件)内部用
this.xxx = y且this === window,就会悄悄写入全局 - 第三方统计脚本、广告 SDK 多数仍基于非模块写法,它们是当前最常见的污染源头
你检查自己写的模块没污染,不代表整个页面安全。
通过控制台快速验证是否存在意外绑定
打开浏览器 DevTools 控制台,运行这几行就能发现异常:
Object.keys(window).filter(k => k.startsWith('_') || k.length —— 看是否有可疑短名或下划线前缀变量-
console.log(window.myTool, window.utils, window.config)—— 检查常用命名空间是否被覆盖 -
getOwnPropertyDescriptor(window, 'xxx')查看属性的configurable和enumerable是否为false(正常模块不会设这些)
如果某个变量在模块里定义、又能在控制台直接敲 window.xxx 访问到,那一定是某处做了显式赋值或用了非模块加载方式。
构建产物和动态注入也可能绕过模块隔离
即使源码全用 ESM,构建工具或运行时行为仍可能引入污染:
- Webpack/Vite 的
library配置若设为umd或var,会把导出内容自动挂到window - 通过
document.write()或innerHTML注入的脚本仍以非模块方式执行 - 服务端渲染(SSR)时,某些框架会在首屏 HTML 中内联
<script>片段,这些片段默认无模块隔离
污染不是只发生在手写 JS 里,它藏在工具链、部署方式和第三方集成的缝隙中。

















