JavaScript 加载与执行机制

2 阅读2分钟
特性普通 scriptdeferasync
下载阻塞 HTML 解析是否否
执行时阻塞 HTML 解析是解析完成后执行可能
保证执行顺序是是否
典型用途必须同步执行的脚本业务主脚本独立第三方脚本

理解普通 script、async、defer 的核心,就是区分 JavaScript 的「下载阶段」和「执行阶段」。

还需要关注一个关键问题:JS 在什么时机执行?

一、三者的区别

类型JS 下载阶段JS 执行时机执行是否阻塞 HTML 解析
普通 script阻塞 HTML 解析下载完成立即执行是
async不阻塞 HTML 解析下载完成后尽快执行可能
defer不阻塞 HTML 解析HTML 解析完成后执行否

注意:这里讨论的是经典外部脚本。

二、通过时间线理解

1. 普通 script

<script src="main.js"></script>
HTML 解析 ─────┐                 ┌────── 继续解析
               │                 │
JS 下载        └───────┐         │
                      │         │
JS 执行               └─────────┘

遇到脚本后,HTML 解析暂停,等待 JS 下载并执行完成。

2. async

<script src="main.js" async></script>
HTML 解析 ─────────────────┐       ┌──── 继续解析
                          │       │
JS 下载   ─────────┐       │       │
                  │       │       │
JS 执行           └───────┴───────┘

JS 下载和 HTML 解析并行,但下载完成后,JS 执行可能打断 HTML 解析。

3. defer

<script src="main.js" defer></script>
HTML 解析 ──────────────────────┐
                               │
JS 下载   ──────────────┐      │
                       │      │
JS 执行                │      └─────────
                       │
                  等待 HTML 解析完成

JS 下载和 HTML 解析并行,等 HTML 解析完成后再执行。

三、还有一个特别容易混淆的地方

不阻塞 HTML 解析 ≠ 不阻塞页面渲染。

例如:

<script src="main.js" defer></script>

虽然 defer 不阻塞 HTML 解析,但如果 main.js 中存在:

while (true) {}

JS 执行时仍然会持续占用主线程,导致页面无法正常更新和响应交互。

因此,需要区分三个概念:

  • 下载阻塞:下载 JS 时,是否暂停 HTML 解析。
  • 执行阻塞:执行 JS 时,是否暂停 HTML 解析。
  • 渲染阻塞:JS 执行是否妨碍浏览器完成页面绘制。

四、面试记忆口诀

普通 script:下载等,执行也等。

async:下载不等,下载完就执行。

defer:下载不等,HTML 解析完再执行。

最后补充一个细节:async 不保证多个脚本的执行顺序,而 defer 会按照它们在 HTML 中出现的顺序执行。

这也是两者除了执行时机之外的重要区别。