浏览器资源加载时序与阻塞行为解析

1.前言

本文通过构造包含本地与远程脚本、图片等资源的 HTML 页面,结合人为延迟加载(1000ms),实测浏览器在解析文档过程中的资源加载顺序与脚本执行阻塞行为。重点验证以下问题

  • 同步 script 标签是否阻塞 HTML 解析?
  • head 与 body 中的脚本执行顺序如何?
  • DOMContentLoaded 与 window.onload 的触发时机与资源加载的关系。

2.从输入 URL 到页面呈现:发生了什么?

当用户在浏览器地址栏输入 URL 并回车后,浏览器会依次执行以下步骤:

  • DNS 解析
    将域名(如 example.com)解析为对应的 IP 地址。解析顺序通常为:

    浏览器缓存 → 操作系统缓存 → 路由器缓存 → 本地 DNS 服务器 → 根域名服务器(递归查询)

  • 建立 TCP 连接(三次握手):

    • 客户端向服务器发送 SYN 报文(SYN=1),请求建立连接;
    • 服务器收到后回复 SYN-ACK 报文(SYN=1, ACK=1),表示同意连接;
    • 客户端再发送 ACK 报文(ACK=1)确认,至此 TCP 连接建立成功。
  • 发送 HTTP 请求
    浏览器通过已建立的 TCP 连接,按照 HTTP/HTTPS 协议发送请求(如 GET /index.html)

  • 服务器处理并返回响应
    服务器接收请求,处理逻辑(如读取文件、调用数据库等),生成 HTTP 响应(含状态码、响应头、HTML 内容等)并返回

  • 浏览器解析与渲染

    • 接收 HTML 后,浏览器开始解析 HTML 构建 DOM 树;
    • 遇到 CSS 则构建 CSSOM 树;
    • 合并 DOM 与 CSSOM 生成 Render Tree;
    • 执行布局(Layout)与绘制(Paint),最终将页面呈现给用户
  • 关闭 TCP 连接(四次挥手)

3.实验设计思路(script 的加载与执行机制)

为了探究浏览器解析 HTML 和加载脚本的顺序,我在 head 和 body 中分别插入:

  • 本地内联脚本;
  • 远程脚本(通过本地服务器提供,每个脚本人为延迟 1000ms 模拟网络耗时);
  • 多张远程图片(同样延迟加载)。
    通过 console.log 输出时间戳,并监听 DOMContentLoaded 与 window.onload 事件,观察执行时序

关键代码片段(简化展示)

<head>
  <script>console.log('head script 1', Date.now())</script>
  <script src="http://127.0.0.1:80/script0.js"></script> <!-- 延迟 1s -->
  <script>console.log('head script 2', Date.now())</script>
</head>
<body>
  <img src="http://127.0.0.1:80/image1.jpg"> <!-- 延迟 1s -->
  ...
</body>
<script>console.log('body script before', Date.now())</script>
<script src="http://127.0.0.1:80/script1.js"></script> <!-- 延迟 1s -->
<script>console.log('body script after', Date.now())</script>

同时监听两个关键事件:

document.addEventListener('DOMContentLoaded', () => {
  console.log('DOMContentLoaded', Date.now());
});
window.onload = () => {
  console.log('window.onload', Date.now());
};

4.实验观察与结论

资源加载是分组并行的

浏览器会将同域资源按并发限制(通常 6 个/域名)分批请求。如图所示,资源被分成多组,每组并行加载,前一组完成后再发起下一组请求(实际行为受 HTTP/2 多路复用影响,但本实验基于 HTTP/1.1)。

script 是阻塞式加载与执行

  • 浏览器按 HTML 顺序解析文档;
  • 遇到 script(无论位于 head 还是 body)时:
    • 若为外部脚本(src 属性),则暂停 HTML 解析,下载并执行该脚本;
    • 若为内联脚本,则立即执行;
  • 所有脚本必须按顺序执行:前一个脚本未加载/执行完,后续脚本(包括 HTML 解析)会被阻塞。
    因此,执行顺序为:

head 内联 → head 远程脚本(逐个等待)→ head 后续内联 → body HTML 渲染 → body 内联 → body 远程脚本(逐个等待)→ ...

DOMContentLoaded 与 window.onload 的触发时机

  • DOMContentLoaded:
    当初始 HTML 文档完全加载和解析完成,且所有同步脚本(非 async/defer)执行完毕时触发。
    此时不等待图片、样式表、iframe 等外部资源加载完成。
  • window.onload:
    当页面所有资源(包括图片、视频、样式、脚本等)全部加载完毕后触发

DOMContentLoaded 在所有同步脚本执行后触发,而 window.onload 在所有远程资源(如图片)加载完成后才触发。

posted @ 2024-05-22 23:13  ---空白---  阅读(55)  评论(0)    收藏  举报