有两个 <script> 特性 defer 和 async可以影响脚本的加载执行时机。
-
defer特性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML,构建 DOM。脚本会“在后台”下载,然后等 DOM 构建完成后,脚本才会执行。具有
defer特性的脚本总是要等到 DOM 解析完毕,但在DOMContentLoaded事件之前执行。 -
async脚本会在后台加载,并在加载就绪时运行。DOM 和其他脚本不会等待它们,它们也不会等待其它的东西。async脚本就是一个会在加载完成时执行的完全独立的脚本。
HTML 页面加载的生命周期有:
-
DOMContentLoaded—— 浏览器已完全加载 HTML,并构建了 DOM 树,但像<img>和样式表之类的外部资源可能尚未加载完成。 -
load—— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。
那么 defer 和 async 脚本的执行与 DOMContentLoaded 和 load 事件的触发时机的先后顺序是怎样的呢?
带着这样的疑惑,借助最近大火的 DeepSeek-R1 深度思考模型帮我解答,果然很强大,以下是 DeepSeek-R1 的回答:
在Web开发中,使用async属性加载的外部脚本(<script async src="..."></script>)的执行顺序与window.onload事件的触发顺序如下:
-
async脚本的加载与执行
async脚本的加载是异步的,不会阻塞HTML解析。脚本一旦下载完成,会立即执行,此时可能会中断HTML解析(若解析尚未完成)。由于浏览器并行下载脚本和其他资源,因此其执行时机取决于下载完成的时间。 -
window.onload的触发条件
window.onload事件会在页面所有资源(包括图片、样式表、异步脚本等)加载完成后触发。这意味着浏览器会等待async脚本的下载完成,即使它们的执行已经结束。 -
执行顺序结论
async脚本的执行一定在window.onload之前。
无论async脚本下载速度快慢,其执行总是在下载完成后立即进行,而window.onload必须等待所有资源(包括该脚本)加载完毕才会触发。- 若脚本下载较慢:当其他资源已加载完毕,但
async脚本仍在下载时,window.onload会等待直到脚本下载完成并执行后才会触发。 - 若脚本下载较快:脚本可能在页面解析阶段执行完毕,此时
window.onload仍需等待其他资源(如图片)加载完成才会触发。
总结:
async外部脚本的执行始终优先于window.onload事件的触发。
在Web开发中,async脚本与DOMContentLoaded事件的触发顺序取决于脚本的下载完成时机,存在以下两种情况:
1. 若async脚本下载完成时,HTML解析尚未完成
async脚本会立即执行,此时会阻塞HTML解析。DOMContentLoaded事件会延迟到脚本执行完成后触发(因为HTML解析被阻塞,解析完成时间被推迟)。
示例场景:
假设async脚本文件很小(如1KB),下载速度极快,可能在HTML解析到</body>前就已下载完成。此时脚本立即执行,HTML解析暂停,DOMContentLoaded需等待脚本执行完毕才会触发。
2. 若async脚本下载完成时,HTML解析已经完成
DOMContentLoaded会立即触发(因为HTML解析已结束)。async脚本在下载完成后才会执行(此时DOMContentLoaded已触发)。
示例场景:
假设async脚本文件较大(如1MB),下载耗时较长。当HTML解析完成时,脚本仍在下载中,此时DOMContentLoaded立即触发。脚本可能在稍后下载完成并执行。
关键逻辑总结
| 场景 | async脚本下载完成时机 | DOMContentLoaded触发时机 | 执行顺序 |
|---|---|---|---|
| 1 | HTML解析完成前 | 延迟到脚本执行后触发 | async脚本 → DOMContentLoaded |
| 2 | HTML解析完成后 | 立即触发 | DOMContentLoaded → async脚本 |
验证实验
你可以通过以下代码测试不同场景(需控制脚本大小或网络速度):
<!DOCTYPE html>
<html>
<head>
<script async src="slow-script.js"></script> <!-- 假设此脚本下载较慢 -->
</head>
<body>
<!-- 页面内容 -->
<script>
document.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded触发");
});
</script>
</body>
</html>
- 若
slow-script.js下载快于HTML解析:控制台输出顺序为async脚本执行 → DOMContentLoaded触发。 - 若
slow-script.js下载慢于HTML解析:控制台输出顺序为DOMContentLoaded触发 → async脚本执行。
根本原因
DOMContentLoaded的触发条件是HTML解析完成(不等待样式表、图片等资源,但会等待同步脚本和未标记async/defer的脚本)。async脚本的加载是异步的,但执行时会阻塞主线程。若脚本在HTML解析完成前下载完毕,则会立即执行并推迟DOMContentLoaded;若在解析完成后下载,则DOMContentLoaded不受影响。
结论
async脚本与DOMContentLoaded的触发顺序没有固定先后,完全取决于脚本的下载速度与HTML解析速度的竞争关系。若需确保脚本在DOMContentLoaded前执行,应使用无属性脚本(同步阻塞)或defer(但需注意defer会保证按顺序执行)。
以上就是 DeepSeek-R1 的回答,可见结论就是:
defer->DOMContentLoaded->window.onload
async->window.onload