全局执行上下文在脚本加载完成瞬间由js引擎触发创建,其时机取决于脚本加载方式:默认同步脚本阻塞html解析并立即初始化;async脚本异步下载但执行时仍抢占上下文且顺序不确定;defer脚本延迟至dom就绪后按序初始化;模块脚本启用隔离的模块级上下文,不污染全局。

全局执行上下文的创建和脚本阻塞密切相关——它不是在 DOM 就绪后才启动,而是在脚本**加载完成的瞬间**由 JS 引擎主动触发。这意味着:脚本何时被加载、以什么方式加载,直接决定了全局上下文何时初始化、哪些变量/函数被提前挂载、以及后续脚本能否安全访问它们。
默认脚本会强制中断 HTML 解析并立即创建上下文
没有 async 或 defer 的 <script src="a.js"></script> 一被解析到,浏览器就暂停 DOM 构建,同步下载、解析、执行。这个过程里,JS 引擎立刻为该脚本创建全局执行上下文,完成变量提升(var 声明设为 undefined,function 声明挂载到 window)、执行初始化代码。如果此时 DOM 还没构建完,document.getElementById 很可能返回 null;多个此类脚本还会串行阻塞,总延迟叠加。
- 内联脚本(
<script>console.log(x)</script>)同样立即触发上下文创建,必然阻塞 - 即使脚本内容为空或全是注释,HTTP 请求本身也会引发阻塞
- 所有无属性脚本按 HTML 顺序依次参与同一轮全局上下文初始化流程
async 脚本不阻塞解析,但执行时仍抢占上下文
async 只把“下载”移出主线程,执行阶段依然会中断 HTML 解析,并立即创建自己的全局执行上下文。由于执行时机不可控(谁先下完谁先执行),不同 async 脚本的上下文创建顺序与 HTML 书写顺序无关。
- 脚本 A 若依赖脚本 B 声明的
window._(如 lodash),而 B 还没加载完,A 执行时就会报ReferenceError - async 对内联脚本无效,只作用于带
src的外部脚本 - 适合完全独立的代码:统计埋点、错误监控、广告 SDK —— 它们不读写 DOM,也不依赖其他 JS
defer 脚本推迟上下文创建,直到 DOM 就绪
defer 脚本异步下载,但 JS 引擎会等到整个 HTML 解析完成、DOM 树构建完毕后,才统一为所有 defer 脚本按书写顺序创建并执行全局执行上下文。这保证了:
- 执行时 DOM 已完整可用,
document.querySelector稳定返回元素 - 多个 defer 脚本共享同一轮上下文初始化逻辑,变量提升和执行顺序严格保序
- 不会打断渲染流,页面可逐步显示内容
- 仅对外部脚本生效;内联脚本加 defer 会被浏览器忽略
模块脚本(type="module")隔离上下文,不污染全局
带 type="module" 的脚本默认行为类似 defer(不阻塞解析、延迟执行、保序),但它启用的是**模块级执行上下文**,而非传统全局上下文:
- 变量、函数不会自动挂载到
window,不存在全局污染 - 模块间通过
import/export显式依赖,引擎能静态分析依赖图,避免运行时引用错误 - 顶层
await合法,支持异步模块初始化 - 旧版 IE 完全不识别 type="module",需提供降级 fallback











