defer
和 async
是 <script>
标签的两个属性,它们都用于控制外部脚本(带有 src
属性的脚本)的加载和执行方式,以避免阻塞 HTML 文档的解析。它们的主要区别在于脚本的执行时机。
核心区别
特性 | defer | async |
---|---|---|
执行时机 | 在 HTML 文档解析完成后,DOMContentLoaded 事件触发之前执行。 | 在脚本下载完成后立即执行,无论此时 HTML 文档是否解析完毕。 |
执行顺序 | 保证顺序。多个带有 defer 的脚本会按照它们在文档中出现的顺序依次执行。 | 不保证顺序。每个脚本在下载完成后就立即执行,因此执行顺序取决于哪个脚本先下载完,与它们在文档中的顺序无关。 |
与 DOM 的关系 | 脚本执行时,整个 DOM 树已经构建完毕,可以安全地访问和操作 DOM 元素。 | 脚本执行时,DOM 树可能尚未完全构建完毕,因此直接操作 DOM 可能有风险(需要检查 DOM 状态或监听 DOMContentLoaded )。 |
与 DOMContentLoaded 的关系 | 所有 defer 脚本的执行都早于 DOMContentLoaded 事件。 | async 脚本的执行时间不确定,可能在 DOMContentLoaded 之前、之后,或者正好同时。 |
图解执行流程
假设 HTML 文档中有多个 <script>
标签:
<html>
<head>
<script src="script1.js" defer></script>
<script src="script2.js" defer></script>
<!-- 或者 async -->
<script src="scriptA.js" async></script>
<script src="scriptB.js" async></script>
</head>
<body>
<div id="content">页面内容</div>
<!-- HTML 解析继续 -->
</body>
</html>
-
defer
流程:- 浏览器开始解析 HTML。
- 遇到
script1.js
和script2.js
,异步开始下载这两个文件,不阻塞 HTML 解析。 - 继续解析 HTML,构建 DOM。
- HTML 文档解析完成,按顺序执行
script1.js
,然后执行script2.js
。 - 触发
DOMContentLoaded
事件。
-
async
流程:- 浏览器开始解析 HTML。
- 遇到
scriptA.js
和scriptB.js
,异步开始下载这两个文件,不阻塞 HTML 解析。 - 假设
scriptA.js
先下载完,立即暂停 HTML 解析并执行scriptA.js
。 scriptA.js
执行完毕后,继续解析 HTML。- 当
scriptB.js
下载完时,立即暂停 HTML 解析并执行scriptB.js
(即使此时 HTML 还没解析完)。 - 最终 HTML 文档解析完成,触发
DOMContentLoaded
事件
如何选择?
-
使用
defer
:- 当你的脚本依赖于完整的 DOM 结构(例如,需要操作页面上的元素)。
- 当你有多个脚本,并且它们之间有依赖关系,需要按顺序执行(例如,先加载 jQuery,再加载依赖 jQuery 的插件)。
- 这是现代 Web 开发中推荐的默认选择,因为它行为可预测且安全。
-
使用
async
:- 当你的脚本是完全独立的,不依赖于 DOM 或其他脚本(例如,独立的广告代码、分析统计脚本)。
- 当你希望脚本尽可能快地执行,并且不关心执行顺序。
- 适用于那些对页面渲染没有直接影响的“辅助性”脚本。
总结:defer
确保脚本在文档解析后、按顺序执行;async
则让脚本下载完就立刻执行,不保证顺序。在大多数需要操作 DOM 或有依赖关系的场景下,优先选择 defer
。