模块脚本顶层作用域独立于window,声明的变量不会自动挂载到全局对象;混合使用普通script与module时,污染主要来自非模块部分。

模块脚本的顶层作用域不是 window
每个 <script type="module"></script> 都拥有独立的顶层作用域,它既不是 window,也不是全局对象的别名。这意味着你在模块里用 let、const、function 声明的东西,不会自动挂到 window 上——哪怕你写 window.myVar = 1,那也是你主动写的,不是模块默认行为。
常见错误现象:<script>let api = fetch</script> 定义了 api,但 <script type="module">console.log(api)</script> 会报 ReferenceError: api is not defined。这不是加载顺序问题,是作用域根本隔离。
- 模块内声明的
const app = {}在控制台直接输app查不到,必须显式挂到window.app才能被其他非模块脚本访问 -
document.currentScript在模块中永远是null,没法靠它读取当前<script></script>的src或dataset - 模块不继承普通
<script></script>的变量,也不向其暴露变量;混合使用时,“污染”几乎总是来自非模块那一半
import 和 export 不等于全局共享
import 是静态绑定,只在模块图构建阶段解析路径和导出名,运行时不会动态查找或合并变量。两个模块都 import { count } from './state.js',拿到的是同一份导出值的**绑定引用**,但如果你在模块 A 里改 count++,模块 B 看到的是更新后的值;而如果模块 A 里写 count = 42(重新赋值),模块 B 仍看到原值——因为 export let count = 0 导出的是可变绑定,不是拷贝。
容易踩的坑:
- 误以为
import './utils.js'就能让utils.js里的变量“生效”到当前模块——其实它只执行副作用,不自动注入任何符号到作用域 - 在模块里
export const config = { env: 'dev' },又在另一个模块里config.env = 'prod'——这会失败,因为const导出的对象属性可改,但不能重新赋值给config变量本身 - 多个模块都
import { init } from './main.js',但init()被调用多次——说明你没控制单例逻辑,模块加载不等于初始化只跑一次
file:// 协议下 type="module" 直接失效
Chrome、Firefox、Safari 在本地双击打开 HTML 文件(地址栏显示 file:///xxx/index.html)时,会彻底拒绝执行任何 type="module" 脚本,连网络请求都不会发出。控制台可能只报空错误、CORS error 或静默失败——这不是代码错,是浏览器硬限制。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
必须满足以下任一条件才能让模块加载成功:
- 用本地服务器启动:比如
npx serve、python3 -m http.server 8000、VS Code 的 Live Server 插件 - 部署到 HTTP/HTTPS 环境,哪怕是
localhost:3000或127.0.0.1:8080 - 移除
type="module"后脚本能跑,不代表原逻辑没问题——只是退化成传统脚本,作用域和导入机制全变了
跨 script 标签的模块无法共享顶层变量
即使两个 <script type="module"></script> 标签写在同一 HTML 文件里,它们也互不感知对方的顶层声明。比如:
<script type="module">
let version = '1.2.0';
export { version };
</script><script type="module">
// 这里访问不到上面的 version,除非 import
console.log(version); // ReferenceError
</script>
真正安全的变量共享,只有两种方式:
- 通过
import/export显式建立模块依赖关系(推荐) - 手动挂到全局对象上,如
globalThis.sharedConfig = {...},但必须清楚这是主动打破隔离 - 避免用
window.xxx = y,因为window在某些上下文(如 iframe、Shadow DOM)中不可靠;globalThis更健壮
最常被忽略的一点:模块作用域隔离是“默认开启 + 主动维持”的状态。一旦你开始混用普通 <script></script>、第三方 SDK、SSR 内联脚本或 eval,边界就很容易被撕开——隔离不是开关,是需要全程盯住的契约。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










