有两个 <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