<script> 标签的 async 与 defer 属性详解
<script>
标签的 async 和 defer 属性是控制 JavaScript 脚本加载与执行时机的关键机制,它们直接影响页面的渲染性能和用户体验。
正常情况下,浏览器会从顶部开始逐行解析 HTML、css文件,但是遇到 <script>
标签会暂停渲染,而去执行 javascript 代码,也就是说 JavaScript 执行会阻塞 DOM 构建,导致页面渲染延迟。
为了解决这个问题,因此HTML5引入了 async 和 defer 属性,用于控制脚本的加载和执行行为。
async和defer属性简介
使用 async 和 defer 属性的脚本都是异步加载的,都不会阻塞 HTML 文档的解析,但是他们的执行时机是不同的,下面来详细说明一下。
默认行为(无属性)
为了对比,我们需要清楚默认行为是怎样的。
<script src="script.js"></script>
执行时机
同步加载和执行。
流程:
- HTML 解析暂停。
- 下载脚本(阻塞主线程)。
- 执行脚本。
- 恢复 HTML 解析。
影响:
页面渲染会延迟,尤其是在脚本体积大或网络慢的时候。
async 属性
<script async src="script.js"></script>
执行时机
异步加载,立即执行。
流程
- HTML 解析继续,不阻塞。
- 并行下载脚本(不阻塞主线程)。
- 脚本下载完成后,立即暂停 HTML 解析并执行脚本。
- 执行完毕后,恢复 HTML 解析。
特点:
- 多个 async 脚本的执行顺序不确定(取决于下载完成时间)。
- 不保证在 DOMContentLoaded 事件前执行。
适用场景:
独立脚本(如广告、分析工具),不依赖 DOM 或其他脚本。
defer 属性
<script defer src="script.js"></script>
执行时机:
异步加载,延迟执行。
流程:
- HTML 解析继续,不阻塞。
- 并行下载脚本(不阻塞主线程)。
- 等待 HTML 解析完成(DOMContentLoaded 事件触发前),按引入顺序执行脚本。
特点:
- 多个 defer 脚本按书写顺序执行。
- 保证在 DOMContentLoaded 事件前执行完毕。
适用场景:
需要操作 DOM 的脚本(如交互逻辑)。
依赖其他脚本的模块(如库的初始化)。
三者表格对比
特性 | 无属性(默认) | async | defer |
---|---|---|---|
是否阻塞 HTML 解析 | 是 | 否 | 否 |
下载时机 | 立即 | 并行(异步) | 并行(异步) |
执行时机 | 下载后立即执行 | 下载完成后立即执行 | HTML 解析完成后(按顺序) |
执行顺序 | 按引入顺序 | 不确定(按下载完成时间) | 按引入顺序 |
DOMContentLoaded 关系 | 阻塞事件触发 | 可能在事件后执行 | 在事件前执行 |
适用场景 | 一般场景 | 独立脚本 | 依赖脚本或初始化脚本 |
代码示例:
<!DOCTYPE html>
<html>
<head><!-- 1. 立即执行,阻塞后续内容 --><script src="critical.js"></script><!-- 2. 并行下载,HTML 解析完成后按顺序执行 --><script defer src="library.js"></script><script defer src="app.js"></script><!-- 3. 并行下载,下载完成后立即执行(顺序不确定) --><script async src="analytics.js"></script>
</head>
<body><!-- DOM 内容 -->
</body>
</html>
注意事项
- 内联脚本无效
async 和 defer 仅对外部脚本(src 属性)有效,对内联脚本无影响。 - 混合使用时的优先级
async 和 defer 同时存在时,行为等同于 async(现代浏览器)。
旧版浏览器可能忽略 defer,建议避免混用。 - 动态插入的脚本
动态创建的脚本默认行为为 async,可通过 script.async = false 修改。 - 兼容性
async:IE 10+,现代浏览器均支持。
defer:IE 9+,现代浏览器均支持。
性能优化建议
- 关键脚本:放 中,确保尽早执行(如 CSSOM 阻塞脚本)。
- 非关键脚本:使用 defer 或 async,避免阻塞渲染。
- 依赖关系脚本:使用 defer 并按依赖顺序引入。
- 独立脚本:使用 async 加速执行(如广告、第三方插件)。