async defer load DOMContentLoaded 先后顺序梳理

3 阅读4分钟

有两个 <script> 特性 defer 和 async可以影响脚本的加载执行时机。

  • defer 特性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML,构建 DOM。脚本会“在后台”下载,然后等 DOM 构建完成后,脚本才会执行。

    具有 defer 特性的脚本总是要等到 DOM 解析完毕,但在 DOMContentLoaded 事件之前执行。

  • async 脚本会在后台加载,并在加载就绪时运行。DOM 和其他脚本不会等待它们,它们也不会等待其它的东西。async 脚本就是一个会在加载完成时执行的完全独立的脚本。

HTML 页面加载的生命周期有:

  • DOMContentLoaded —— 浏览器已完全加载 HTML,并构建了 DOM 树,但像 <img> 和样式表之类的外部资源可能尚未加载完成。

  • load —— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。

那么 deferasync 脚本的执行与 DOMContentLoadedload 事件的触发时机的先后顺序是怎样的呢?

带着这样的疑惑,借助最近大火的 DeepSeek-R1 深度思考模型帮我解答,果然很强大,以下是 DeepSeek-R1 的回答:

在Web开发中,使用async属性加载的外部脚本(<script async src="..."></script>)的执行顺序与window.onload事件的触发顺序如下:

  1. async脚本的加载与执行
    async脚本的加载是异步的,不会阻塞HTML解析。脚本一旦下载完成,会立即执行,此时可能会中断HTML解析(若解析尚未完成)。由于浏览器并行下载脚本和其他资源,因此其执行时机取决于下载完成的时间。

  2. window.onload的触发条件
    window.onload事件会在页面所有资源(包括图片、样式表、异步脚本等)加载完成后触发。这意味着浏览器会等待async脚本的下载完成,即使它们的执行已经结束。

  3. 执行顺序结论

    • 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触发时机执行顺序
1HTML解析完成前延迟到脚本执行后触发async脚本 → DOMContentLoaded
2HTML解析完成后立即触发DOMContentLoadedasync脚本

验证实验

你可以通过以下代码测试不同场景(需控制脚本大小或网络速度):

<!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