如果一个脚本既有 async 又有 defer 属性,会发生什么情况?
核心答案
async 优先级更高,defer 会被忽略。 当一个 <script> 标签同时具有 async 和 defer 属性时,浏览器会按照 async 的行为执行——脚本并行下载,下载完成后立即执行,不保证执行顺序。
这是 HTML 规范明确定义的行为,defer 在这种情况下作为降级回退存在,用于兼容不支持 async 的老旧浏览器。
深入解析
HTML 规范中的优先级
根据 HTML Living Standard,浏览器处理 <script> 标签的逻辑如下:
if (脚本有 src 属性) {
if (async 属性存在) {
→ 使用 async 模式
} else if (defer 属性存在) {
→ 使用 defer 模式
} else {
→ 使用传统阻塞模式
}
}
关键点:async 的判断在 defer 之前,所以 async 优先。
为什么要这样设计?
这是一个优雅降级的设计:
| 浏览器支持情况 | 行为 |
|---|---|
| 支持 async | 使用 async(忽略 defer) |
| 不支持 async,支持 defer | 使用 defer |
| 都不支持 | 传统阻塞加载 |
在 async 刚推出时(约 2010 年),老版本 IE(IE9 及以下)不支持 async 但支持 defer。同时写两个属性可以让:
- 现代浏览器使用 async
- 老浏览器回退到 defer
三种模式对比
下载 执行时机 顺序保证 阻塞解析
无属性 阻塞 下载完立即执行 ✅ ✅
async 并行 下载完立即执行 ❌ ❌
defer 并行 DOM 解析完成后 ✅ ❌
async + defer 并行 下载完立即执行 ❌ ❌
常见误区
误区 1: "两个属性会产生某种组合效果"
- 错误。不存在 "async-defer" 混合模式,只会选择其中一个
误区 2: "defer 会覆盖 async"
- 错误。恰恰相反,async 优先级更高
误区 3: "现代开发中同时写两个属性有意义"
- 基本没有意义了。async 的浏览器支持率已经非常高(IE10+),不需要 defer 作为回退
内联脚本的特殊情况
<!-- async 和 defer 对内联脚本无效 -->
<script async defer>
console.log('我是内联脚本,async 和 defer 都被忽略');
</script>
async 和 defer 只对外部脚本(有 src 属性)有效。
代码示例
<!-- 同时有 async 和 defer -->
<script async defer src="script.js"></script>
<!-- 等价于(在现代浏览器中) -->
<script async src="script.js"></script>
验证行为的测试代码
<!DOCTYPE html>
<html>
<head>
<script async defer src="a.js"></script> <!-- 输出 A -->
<script async defer src="b.js"></script> <!-- 输出 B -->
<script async defer src="c.js"></script> <!-- 输出 C -->
</head>
<body>
<script>
document.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded');
});
</script>
</body>
</html>
<!--
可能的输出顺序(取决于下载速度):
B, A, C, DOMContentLoaded
或
A, C, B, DOMContentLoaded
或其他任意顺序
如果是纯 defer,输出一定是:
A, B, C, DOMContentLoaded
-->
实际应用场景
<!-- 2010-2015 年的兼容性写法 -->
<script async defer src="analytics.js"></script>
<!-- 现代写法:直接用 async 或 defer -->
<!-- 独立脚本(如统计、广告)用 async -->
<script async src="analytics.js"></script>
<!-- 有依赖关系的脚本用 defer -->
<script defer src="vendor.js"></script>
<script defer src="app.js"></script>
面试技巧
可能的追问方向
-
"为什么 async 优先级更高?"
- 这是 HTML 规范的设计,目的是让 defer 作为 async 的降级回退
- 体现了渐进增强/优雅降级的设计思想
-
"现在还需要同时写两个属性吗?"
- 基本不需要。async 支持率已经很高
- 如果要兼容 IE9,应该用其他方案(如 polyfill 或条件注释)
-
"module 类型的脚本呢?"
<script type="module">默认就是 defer 行为- 可以加 async 变成 async 行为
- 不需要显式写 defer
-
"动态创建的脚本呢?"
- 动态创建的
<script>默认是 async 行为 - 可以设置
script.async = false来改变
- 动态创建的
展示深度的回答方式
"当 async 和 defer 同时存在时,async 优先,defer 被忽略。这是 HTML 规范明确定义的行为,设计初衷是让 defer 作为 async 的降级回退——在 async 刚推出时,老版本 IE 不支持 async 但支持 defer,同时写两个属性可以实现优雅降级。不过在现代开发中,这种写法已经没有必要了。"
一句话总结
async + defer = async;defer 只是 async 的降级回退,现代开发中无需同时使用。