跳到主要内容

Script 标签中 defer 与 async 的区别

面试速答(30 秒版 TL;DR)

  • deferasync 都能让脚本下载时 不阻塞 HTML 解析,这是它们相对普通同步脚本的共同点。
  • 核心区别在 执行时机和顺序保证
    • async:下载完就立刻执行,执行时会中断 HTML 解析;多个 async 脚本执行顺序不保证。
    • defer:等 HTML 全部解析完成后再按文档顺序执行;适合主业务脚本。
  • 高频结论:
    • 有依赖关系、需要稳定顺序:用 defer
    • 独立脚本,如统计、埋点、广告:用 async

先看三种脚本的行为差异

脚本类型下载是否阻塞 HTML 解析执行时机是否保证顺序
普通 <script>下载完立即执行按出现顺序
<script async>不会下载完立即执行不保证
<script defer>不会HTML 解析完成后执行保证

时间线是最重要的理解方式


1)普通同步脚本为什么慢?

<script src="/app.js"></script>

浏览器遇到它时通常要做三件事:

  1. 暂停 HTML 解析
  2. 下载脚本
  3. 执行脚本

执行完后才继续往下解析页面。

所以同步脚本特别容易拖慢首屏和 DOM 构建。


2)async 的特点

<script async src="/analytics.js"></script>
<script async src="/ads.js"></script>

特点:

  • 下载过程不阻塞 HTML 解析
  • 谁先下载完谁先执行
  • 执行那一刻会暂停 HTML 解析
  • 多个 async 脚本之间 没有顺序保证

适合:

  • 埋点
  • 广告
  • 独立统计脚本
  • 不依赖 DOM 完整结构、也不依赖其他脚本顺序的资源

不适合:

  • 有前后依赖关系的业务脚本
  • 必须等 DOM 完整后再执行的初始化逻辑

3)defer 的特点

<script defer src="/vendor.js"></script>
<script defer src="/app.js"></script>

特点:

  • 下载过程不阻塞 HTML 解析
  • 要等 HTML 解析完成后再执行
  • 多个 defer 脚本 按文档顺序执行
  • 一般在 DOMContentLoaded 之前执行完

适合:

  • 主业务入口脚本
  • 有先后依赖关系的脚本
  • 需要操作完整 DOM 的初始化逻辑

高频考点:DOMContentLoaded 和它们的关系

你可以这样记:

  • defer 脚本会在 HTML 解析结束后、DOMContentLoaded 触发前执行
  • async 脚本不保证和 DOMContentLoaded 的相对顺序

所以如果面试官问“为什么很多主入口脚本喜欢用 defer”,一个高质量回答是:

因为它既避免了下载阶段阻塞 HTML 解析,又能保证脚本按顺序在 DOM 构建完成后执行,和页面初始化流程更匹配。


典型选择场景

场景 1:主业务脚本

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

理由:

  • 依赖顺序稳定
  • 不阻塞 HTML 解析

场景 2:第三方统计脚本

<script async src="https://example.com/analytics.js"></script>

理由:

  • 越早加载越好
  • 不依赖其他业务脚本顺序

高频追问:defer 和把脚本放在 body 底部有什么区别?

相同点:

  • 都能减少对前面 HTML 解析的阻塞感

不同点:

  • defer 脚本可以更早开始下载,因为浏览器在解析到标签时就会并行拉取
  • 把脚本放在底部,往往是“解析快结束时才开始下载”
  • defer 还能天然表达“这是延迟执行脚本”的语义

工程上现代项目通常更偏向 defer


高频追问:模块脚本呢?

<script type="module" src="/main.js"></script>

面试里一个常见补充点是:

  • type="module" 的脚本在行为上通常更接近延迟执行,不会像传统同步脚本那样阻塞整个 HTML 解析流程
  • 但它还有模块解析、依赖图加载等额外语义,不能简单等同于普通 defer

如果面试官没追问,这一层点到为止就够了。


典型题 & 标准答法

Q1:asyncdefer 的区别是什么?

答法建议:

  • 共同点:下载都不阻塞 HTML 解析
  • 区别:async 下载完立即执行、顺序不保证;defer 等 HTML 解析完再按顺序执行

Q2:主业务脚本为什么一般用 defer

答法建议:

  • 因为主业务脚本通常有依赖顺序,还要操作完整 DOM
  • defer 同时满足“并行下载”和“顺序执行”

Q3:埋点脚本为什么常用 async

答法建议:

  • 因为它通常独立,不依赖其它脚本
  • 越早下载执行越好,没必要等完整 DOM

常见追问

  • async 会不会阻塞解析?
    • 下载阶段不阻塞,但执行阶段会暂停 HTML 解析。
  • defer 一定在 DOMContentLoaded 前执行吗?
    • 经典结论是这样理解最稳,面试里这么答通常足够。
  • 能不能同时写 async defer
    • 在经典脚本语义里通常以 async 行为为主,工程上不要这么写,避免歧义。

易错点 / 坑

  • 把“下载不阻塞”误解成“执行也不阻塞”
  • async 加载有依赖顺序的脚本,导致偶发报错。
  • defer 和“脚本永远最后执行”画等号,忽略它仍属于页面初始化链路的一部分。
  • 把模块脚本简单粗暴等同于普通 defer

速记要点(可背诵)

  • async:独立脚本,下载完就执行,顺序不保证。
  • defer:业务脚本,HTML 解析完再执行,顺序保证。
  • 共同点是下载不阻塞解析,不同点是执行时机和顺序。