在前端开发与性能优化的实践中,<script>标签的async和defer属性都是用于优化外部脚本加载行为、避免阻塞页面渲染的关键属性。尽管它们都能实现脚本加载与HTML解析的并行,但在执行时机、顺序保证和适用场景上存在本质区别。理解这些差异对于编写高性能、可预测的Web页面至关重要。
一、 阻塞问题:为何需要 async/defer?
在默认情况下(即<script>标签不添加任何属性),浏览器的处理流程是线性的、阻塞式的:
- 暂停HTML文档的解析。
- 开始下载
<script>标签指定的外部脚本文件。
- 等待脚本下载完成后,立即执行。
- 脚本执行完毕,才恢复HTML文档的解析。
当脚本文件体积较大或网络状况不佳时,这种阻塞会显著拖慢页面的首次渲染速度,影响用户体验。async和defer属性的核心价值,就在于将脚本的下载过程与HTML解析并行化,从而减少阻塞。两者的区别在于脚本执行的时机。
二、 async 属性:异步加载,立即执行
async属性适用于那些完全独立、不依赖DOM或其他脚本的脚本,例如网站分析、广告或监控代码。
核心工作流程
- 并行下载:浏览器在解析HTML时遇到带
async属性的<script>标签,会立即开始异步下载脚本,同时继续解析后面的HTML内容。
- 立即执行:一旦脚本下载完成,浏览器会立即暂停HTML解析,执行该脚本。
- 恢复解析:脚本执行完毕后,恢复HTML解析。
关键特性与注意事项
- 执行顺序不固定:多个
async脚本的执行顺序取决于它们的下载完成顺序。即使脚本A在HTML中位于脚本B之前,如果B先下载完,也会先执行。
- 依赖关系无法保证:正因执行顺序不确定,
async脚本之间不能存在依赖关系,否则可能导致错误。
- 可能阻塞渲染:虽然下载不阻塞,但脚本执行时依然会阻塞HTML解析。
<!-- 即使 small.js 在 large.js 之后声明,但由于其文件小下载快,可能先执行 -->
<script async src="large.js"></script>
<script async src="small.js"></script>
三、 defer 属性:异步加载,延迟执行
defer属性适用于那些需要操作DOM,或依赖其他脚本的场景,例如页面初始化逻辑、基于jQuery的插件等。
核心工作流程
- 并行下载:与
async类似,浏览器遇到defer脚本时也会并行下载,不阻塞HTML解析。
- 延迟执行:脚本下载完成后不会立即执行,而是被放入一个队列。
- 有序执行:浏览器会等待整个HTML文档解析完成(触发
DOMContentLoaded事件之前),再严格按照脚本在HTML中出现的顺序依次执行所有defer脚本。
关键特性与优势
- 执行时机确定:在所有DOM元素解析完成后才执行,确保脚本可以安全操作DOM。
- 严格保持顺序:多个
defer脚本的执行顺序与它们在HTML文档中的声明顺序完全一致,无论下载快慢。
- 完全不阻塞渲染:从下载到执行的整个过程都不会阻塞HTML的解析与渲染。
- 完美支持依赖:只需将依赖的脚本(如库文件)放在被依赖脚本之前声明,即可确保正确的加载顺序。
<!-- 即使 small.js 先下载完,也会等待 large.js 执行完毕后再执行 -->
<script defer src="large.js"></script>
<script defer src="small.js"></script>
四、 对比总结:async vs defer vs 默认
下表从多个维度清晰对比了三者的核心差异:
| 对比维度 |
默认脚本 (无属性) |
async 脚本 |
defer 脚本 |
| 下载是否阻塞HTML解析 |
是 |
否 |
否 |
| 执行是否阻塞HTML解析 |
是 |
是 (执行时阻塞) |
否 |
| 执行时机 |
下载后立即执行 |
下载后立即执行 |
HTML解析完成后,DOMContentLoaded事件前 |
| 多个脚本的执行顺序 |
按HTML中顺序执行 |
按下载完成顺序,不保证声明顺序 |
严格按HTML中声明顺序执行 |
| 是否适合操作DOM |
不适合(DOM可能未解析) |
不适合(时机不可控) |
适合 (DOM已准备就绪) |
| 是否支持脚本间依赖 |
支持(按顺序) |
不支持 |
支持 (按顺序) |
五、 最佳实践与选择策略
在实际的HTML与JavaScript开发中,请遵循以下原则进行选择:
优先使用 defer
- 脚本需要访问或操作DOM元素。
- 脚本之间存在明确的依赖关系(例如,你的业务代码依赖于某个工具库)。
- 脚本是页面核心功能的一部分,需要稳定的执行环境。
可以考虑使用 async
- 脚本完全独立,不依赖任何其他资源,也不被任何脚本依赖。
- 脚本属于非关键、不影响主体功能的第三方脚本,如数据分析、广告投放代码。
重要注意事项
- 仅对外部脚本有效:
async和defer属性只对带有src属性的外部脚本生效,对内联脚本(<script>// code here</script>)无效。
- 属性冲突:如果同时指定了
async和defer,现代浏览器的行为是async优先级更高,会忽略defer。
- 动态注入的脚本:通过JavaScript动态创建的
<script>元素,其async属性默认为true。如果需要保证动态脚本的执行顺序,需显式设置为async: false。
|