← Frontend

浏览器渲染流程和性能

浏览器渲染流程和性能优化相关内容

本笔记系统梳理从浏览器地址栏输入 URL 到页面最终渲染完成的完整流程

一、整体流程概览

当用户在浏览器地址栏输入一个 URL 并回车后,浏览器会经历以下关键阶段:

阶段 主要工作 涉及协议/技术
1. URL 解析 解析 URL 结构,判断协议、域名、端口、路径 URL 规范
2. DNS 解析 将域名转换为 IP 地址 DNS、UDP
3. TCP 连接 建立可靠的传输层连接(三次握手) TCP
4. 发送 HTTP 请求 构造请求报文并发送 HTTP/HTTPS
5. 服务器处理 后端处理请求并返回响应报文 HTTP
6. 浏览器渲染 解析 HTML/CSS/JS,构建 DOM 树并绘制页面 浏览器渲染引擎
7. 断开连接 关闭 TCP 连接(四次挥手) TCP

二、URL 输入与浏览器解析

2.1 URL 的组成结构

以 https://www.example.com:443/path/page?id=1#section 为例:

https://    www.example.com    :443    /path/page    ?id=1    #section
  ↓              ↓               ↓          ↓           ↓         ↓
协议         域名(host)         端口      路径(path)   查询参数   锚点
  • 协议(Protocol):决定使用什么方式传输数据(http / https / ftp 等)
  • 域名(Host):服务器地址,需通过 DNS 解析为 IP
  • 端口(Port):http 默认 80,https 默认 443
  • 路径(Path):服务器上资源的具体位置
  • 查询参数(Query):以 ? 开头,键值对形式传递参数
  • 锚点(Hash):以 # 开头,不会发送到服务器,仅用于前端定位

2.2 浏览器输入后的预处理

  1. 判断输入内容:是搜索关键词还是合法 URL?若不是合法 URL,则交给默认搜索引擎
  2. HSTS 检查:检查域名是否在 HSTS(HTTP Strict Transport Security)列表中,若在则强制升级为 HTTPS
  3. 检查缓存:浏览器按以下顺序查找资源
    • Service Worker 缓存
    • Memory Cache(内存缓存)
    • Disk Cache(磁盘缓存)
    • Push Cache(HTTP/2 推送缓存)
  4. 构造完整请求:补全协议、端口等缺省信息

💡 提示: 若强缓存(Cache-Control / Expires)命中,浏览器会直接使用本地资源,根本不会发起网络请求。


三、DNS 解析

3.1 什么是 DNS

DNS(Domain Name System,域名系统)的作用是将人类可读的域名(如 www.example.com)翻译成机器可识别的 IP 地址(如 93.184.216.34)。

3.2 DNS 解析的完整流程

DNS 查询采用递归查询 + 迭代查询结合的方式,查找顺序如下:

1. 浏览器 DNS 缓存
   ↓ 未命中
2. 操作系统 DNS 缓存(包括 hosts 文件)
   ↓ 未命中
3. 本地 DNS 服务器(通常是运营商 ISP 提供)
   ↓ 未命中
4. 根域名服务器(Root DNS Server)
   ↓ 返回顶级域服务器地址
5. 顶级域名服务器(TLD,如 .com / .cn)
   ↓ 返回权威域名服务器地址
6. 权威域名服务器(Authoritative DNS Server)
   ↓ 返回最终 IP
7. 拿到 IP,逐级缓存返回给浏览器

3.3 DNS 优化策略

优化手段 原理 效果
DNS 缓存 浏览器、操作系统、本地 DNS 多级缓存 减少查询时间
DNS Prefetch 提前对页面中可能用到的域名进行解析 提前完成解析,节省主请求时间
HTTPDNS 用 HTTP 协议替代 UDP 向 DNS 服务器查询 避免 DNS 劫持,结果更准确
减少域名数量 合并资源到同一个域名下 减少 DNS 查询次数
使用 CDN 就近返回边缘节点 IP 降低延迟

DNS Prefetch 用法示例:

<!-- 在 head 中预解析指定域名,浏览器会异步解析这些域名 -->
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="dns-prefetch" href="//api.example.com">

<!-- preconnect 更进一步:除了 DNS 还会建立 TCP 连接 -->
<link rel="preconnect" href="//cdn.example.com" crossorigin>

⚠️ 注意: DNS 解析默认使用 UDP 协议(端口 53),因为 UDP 无连接、速度快;只有数据包过大时才会切换到 TCP。


四、TCP 连接:三次握手与四次挥手

4.1 三次握手(建立连接)

TCP 是面向连接、可靠的传输层协议。在发送数据前必须先建立连接,过程称为"三次握手"。

客户端                                                    服务端
  │                                                         │
  │ ───── 1. SYN (seq=x)                       ────────────►│  [LISTEN]
  │                                                         │
  │                                                  [SYN_RCVD]
  │ ◄──── 2. SYN + ACK (seq=y, ack=x+1)         ────────────│
  │                                                         │
[ESTABLISHED]                                               │
  │ ───── 3. ACK (seq=x+1, ack=y+1)            ────────────►│  [ESTABLISHED]
  │                                                         │
  │                  连接建立,开始传输数据                  │

三步详解:

  1. 第一次握手:客户端发送 SYN(同步)报文,请求建立连接
  2. 第二次握手:服务端回复 SYN + ACK,表示同意建立连接
  3. 第三次握手:客户端再次发送 ACK,连接正式建立

为什么是三次而不是两次?

为了防止已失效的连接请求报文突然到达服务端,使服务端建立无效连接,造成资源浪费。三次握手能确保双方都具备发送和接收能力。

4.2 四次挥手(断开连接)

客户端                                                    服务端
  │                                                         │
  │ ───── 1. FIN (seq=u)                       ────────────►│  
[FIN_WAIT_1]                                                │
  │                                                  [CLOSE_WAIT]
  │ ◄──── 2. ACK (ack=u+1)                      ────────────│
[FIN_WAIT_2]                                                │
  │                                                         │
  │            (服务端还可能有数据要发送)                  │
  │                                                         │
  │ ◄──── 3. FIN (seq=w)                        ────────────│  [LAST_ACK]
  │                                                         │
[TIME_WAIT]                                                 │
  │ ───── 4. ACK (ack=w+1)                     ────────────►│  [CLOSED]
  │                                                         │
  │  等待 2*MSL 后关闭                                       │
[CLOSED]

四步详解:

  1. 第一次挥手:客户端发送 FIN,表示数据已发完,请求断开
  2. 第二次挥手:服务端返回 ACK,表示收到关闭请求(但服务端可能还有数据要传)
  3. 第三次挥手:服务端处理完后发送 FIN,表示自己也要关闭
  4. 第四次挥手:客户端返回 ACK,进入 TIME_WAIT 状态,等待 2*MSL 后真正关闭

为什么挥手要四次?

因为 TCP 是全双工的,关闭连接需要双方各自确认。服务端收到 FIN 后不能立刻关闭,可能还有数据没发完,所以 ACK 和 FIN 分两次发送。

🚨 警告: TIME_WAIT 状态会持续 2*MSL(Maximum Segment Lifetime,通常 60s 左右),高并发场景下可能导致端口耗尽。

4.3 具体实现过程

本节从 TCP 协议实现层面补充 4.1 / 4.2 的抽象描述,关注报文标志位与状态流转细节。

TCP 传输服务

TCP(Transmission Control Protocol,传输控制协议)提供面向连接的、可靠的字节流服务。这句话拆开来理解:

特性 含义
面向连接 通信前必须先建立连接(三次握手),通信后必须释放连接(四次挥手),类似打电话要先拨号、最后挂断
可靠传输 通过 Sequence Number(序号)、ACK(确认应答)、超时重传等机制,保证数据不丢、不乱序、不重复
字节流服务 数据被视为无结构的字节序列,由 TCP 负责拆包、组包,上层应用不需要关心分片细节

三次握手 — 实现细节

每一步都涉及 TCP 标志位和序列号的精确控制:

  1. 第一次握手

    • 客户端发送 SYN 包:SYN=1,序列号 Seq=x(随机生成)
    • 客户端进入 SYN_SEND 状态,等待服务端确认
    • SYN(Synchronize Sequence Number):同步序列号,用于发起连接请求

  2. 第二次握手

    • 服务端收到 SYN 包后,回复 SYN + ACK 包
    • 其中 SYN=1, ACK=1,确认号 Ack=x+1,同时自己也发送序列号 Seq=y
    • 服务端进入 SYN_RECV(同步已接收)状态
    • ACK(Acknowledgment):确认应答,表示已收到对方的报文

  3. 第三次握手

    • 客户端收到 SYN+ACK 包后,向服务端发送确认包 ACK
    • 其中 ACK=1,确认号 Ack=y+1
    • 此包发送完毕后,双方进入 ESTABLISHED(已建立)状态,完成三次握手

💡 提示: SYN 包中的序列号 x 和 y 均为随机生成,目的是防止序列号预测攻击(Sequence Number Prediction Attack)。

四次挥手 — 实现细节

四次挥手的每一步同样依赖标志位和序列号控制状态流转:

  1. 第一次挥手

    • 客户端(主动关闭方)发送 FIN 包:FIN=1,序列号 Seq=u
    • 表示客户端 → 服务端方向的数据传输已结束
    • 客户端进入 FIN_WAIT_1(终止等待-1)状态
    • FIN(Finish):结束标志,表示发送方数据已发完,请求释放连接

  2. 第二次挥手

    • 服务端收到 FIN 包后,发送 ACK 确认包:ACK=1,确认号 Ack=u+1,同时携带自己的序列号 Seq=v
    • 此时客户端 → 服务端方向已关闭,但服务端 → 客户端方向仍可继续发送数据
    • 服务端进入 CLOSE_WAIT(关闭等待)状态;客户端收到 ACK 后进入 FIN_WAIT_2 状态
  3. 第三次挥手

    • 服务端将剩余数据发送完毕后,发送 FIN 包:FIN=1,序列号 Seq=w
    • 表示服务端也准备好关闭连接
    • 服务端进入 LAST_ACK(最后确认)状态,等待客户端最终确认
  4. 第四次挥手

    • 客户端收到 FIN 后,发送 ACK 确认包:ACK=1,确认号 Ack=w+1
    • 客户端进入 TIME_WAIT(时间等待)状态,等待 2 × MSL 后进入 CLOSED
    • 服务端收到 ACK 后立即进入 CLOSED 状态

🚨 关键: MSL(Maximum Segment Lifetime,最大报文生存时间),通常为 30s ~ 60s。客户端等待 2 × MSL 的原因有两点:

  • 确保最后一个 ACK 能被服务端收到(若丢失,服务端会在 2MSL 内重发 FIN)
  • 让旧连接的所有残余报文在网络中彻底消失,避免干扰同一端口上的新连接

4.4 HTTPS 的额外步骤:TLS 握手

如果是 HTTPS 请求,在 TCP 三次握手之后还需要进行 TLS 握手:

步骤 内容
1. ClientHello 客户端发送支持的 TLS 版本、加密套件、随机数
2. ServerHello 服务端选择加密套件,返回证书和随机数
3. 证书验证 客户端验证服务端证书合法性
4. 密钥交换 通过非对称加密协商出对称加密密钥
5. Finished 双方完成握手,开始用对称加密通信

五、发送 HTTP 请求

5.1 HTTP 请求报文结构

GET /index.html HTTP/1.1                  ← 请求行(方法 + 路径 + 协议版本)
Host: www.example.com                     ← 请求头
User-Agent: Mozilla/5.0 ...
Accept: text/html,application/xhtml+xml
Accept-Encoding: gzip, deflate, br
Cookie: sessionId=abc123
                                          ← 空行(分隔头部与正文)
(请求体,GET 通常为空)                     ← 请求体

5.2 常见请求方法对比

方法 用途 幂等性 安全性 是否有请求体
GET 获取资源 ✅ ✅ ❌
POST 创建资源 ❌ ❌ ✅
PUT 全量更新资源 ✅ ❌ ✅
PATCH 部分更新资源 ❌ ❌ ✅
DELETE 删除资源 ✅ ❌ ❌
HEAD 仅获取响应头 ✅ ✅ ❌
OPTIONS 查询服务端支持的方法 ✅ ✅ ❌

💡 提示: 幂等性指多次请求与一次请求的效果相同;安全性指请求不会改变服务端状态。

5.3 HTTP缓存

5.3.1 强缓存

  • 强缓存如果命中不会向服务器发送请求,直接读取本地数据;

  • 状态码返回 200 (from memory cache) 或者 200 (from disk cache)

  • from memory cache 内存缓存,小文件或者频繁访问的文件

  • from disk cache 磁盘缓存,一般存的大文件

  • 强缓存可以通过两种响应头来配置:Cache-Control 和 Expires;

  • **Cache-Control:**max-age=缓存多少秒

  • Expires:过期时间

注意:如果同时设置了Cache-Control 和 Expires 时 Cache-Control 优先级更高,因为 Expires使用的是具体的日期和时间,客户端和服务器之间可能存在时间不同步的情况,Cache-Control 设置的是缓存的秒数,不存在这种问题。

5.3.2 协商缓存

描述: 当客户端请求资源时,如果该资源已经被缓存在本地,客户端会向服务器发送一个带有本地缓存的相关信息的请求(如Last-Modified和ETag),询问服务器资源是否有更新,如果有更新的话就返回最新的资源,没有更新时返回 304 状态码,通知浏览器可以使用本地缓存的资源。

具体的工作原理如下:

  1. 当客户端首次请求某个资源时,服务器会在响应头中携带文件最后修改时间(Last-Modified)或唯一内容标识(ETag)。
  2. 当客户端再次请求相同的资源时,会在请求头中把之前服务器返回的 Last-Modified 或 ETag 通过 If-Modified-Since 和 If-None-Match 字段传给服务器。
  • If-Modified-Since 传 Last-Modified
  • If-None-Match 传 ETag
  1. 服务器接收到请求后,会比较客户端传入的 Last-Modified 或 ETag 与服务器上的是否一致。
  • 如果两者一致,服务器会返回 304 状态码,浏览器可以直接从本地缓存中读取。
  • 如果不一致,服务器会返回 200,和最新的资源内容。

5.3.3 协商缓存为什么有 Last-Modified 还要有 ETag

协商缓存中同时使用 Last-Modified 和 ETag 是为了解决单一机制存在的局限性,两者互为补充。以下是主要原因:

Last-Modified 的局限性
  1. 精度问题:Last-Modified 只能精确到秒级,如果文件在1秒内多次修改无法识别
  2. 内容不变时间变:文件内容没变但修改时间变了(如touch操作),会导致不必要的重新下载
  3. 无法识别重命名:文件被重命名但内容相同,Last-Modified 会认为资源已改变
  4. 分布式系统时间同步问题:不同服务器时间不一致可能导致缓存失效判断错误
ETag 的优势
  1. 更精确的变更检测:ETag 是基于文件内容生成的哈希值或版本号,能精确识别内容变化
  2. 解决时间精度问题:不受时间精度限制,能检测1秒内的多次修改
  3. 处理特殊情况:能正确处理仅修改时间但内容不变的情况
  4. 分布式系统友好:不依赖服务器时间,适合分布式环境
实际应用中的配合
  • 服务器通常同时发送 Last-Modified 和 ETag
  • 客户端在后续请求中可能同时发送 If-Modified-Since 和 If-None-Match
  • 服务器优先验证 ETag,若匹配则直接返回304,不匹配再检查 Last-Modified
  • 这种双重验证机制提高了缓存判断的准确性

5.3.4 浏览器加载缓存资源的流程

  1. 浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用本地资源;
  2. 如果不命中则根据头信息向服务器发起请求,服务器判断是否命中协商缓存;
  3. 如果协商缓存命中的话,则服务器状态码返回304,不返回资源,浏览器直接使用本地资源;
  4. 如果协商缓存不命中,则服务器返回最新的资源给浏览器。

5.4 HTTP/1.1 vs HTTP/2 vs HTTP/3

了解 HTTP 版本演进,是网络优化的基础。

HTTP/1.1 的问题

问题 说明
队头阻塞(HOL Blocking) 同一连接内请求串行发送,前一个慢了后面全等
连接数限制 浏览器对同一域名默认最多 6 个并发连接
Header 冗余 每次请求都携带完整 Header,无压缩
无服务端推送 服务端只能被动响应,不能主动推送资源

HTTP/2 的核心改进

特性 说明
多路复用(Multiplexing) 单个 TCP 连接中并行处理多个请求/响应,彻底解决队头阻塞
Header 压缩(HPACK) 使用 HPACK 算法压缩 Header,减少冗余传输
服务端推送(Server Push) 服务端可主动推送客户端需要的资源(如 CSS、JS)
二进制分帧(Binary Framing) 将数据分割为二进制帧传输,解析效率更高
请求优先级 可以给请求设置优先级,重要资源优先传输
HTTP/1.1:连接 A → [请求1] → [响应1] → [请求2] → [响应2](串行)

HTTP/2:
  连接 A →  [请求1]  [请求2]  [请求3]  (并行,多路复用)
           ↓         ↓         ↓
          [响应1]   [响应2]   [响应3]

HTTP/3

HTTP/3 基于 QUIC 协议(基于 UDP),解决了 HTTP/2 的 TCP 层队头阻塞:

HTTP/2 HTTP/3
基于 TCP(可靠但有 TCP 队头阻塞) 基于 QUIC/UDP(UDP 层面实现可靠性)
TLS 握手 + TCP 握手分开 0-RTT 或 1-RTT 建立连接(更快)
网络切换需重建连接 Connection ID 机制,网络切换无感知

💡 实际影响: HTTP/2 在弱网环境下可能比 HTTP/1.1 更慢(一个 TCP 包丢失影响所有流);HTTP/3 的 QUIC 从根本上解决了这个问题。


六、服务器处理请求并返回响应

6.1 服务器处理流程

1. 接收 HTTP 请求
   ↓
2. 反向代理(如 Nginx)转发到应用服务器
   ↓
3. 应用服务器(Tomcat / Node.js / FastAPI 等)路由匹配
   ↓
4. 中间件处理(鉴权、日志、限流等)
   ↓
5. 业务逻辑处理(查询数据库、调用其他服务)
   ↓
6. 构造响应报文返回

6.2 HTTP 响应报文结构

HTTP/1.1 200 OK                           ← 状态行(协议 + 状态码 + 状态描述)
Content-Type: text/html; charset=UTF-8    ← 响应头
Content-Length: 1024
Cache-Control: max-age=3600
Set-Cookie: sessionId=xyz789
                                          ← 空行
<!DOCTYPE html>                           ← 响应体
<html>...</html>

6.3 常见 HTTP 状态码

状态码 类别 含义
1xx 信息响应 请求已被接收,继续处理
200 成功 请求成功
204 成功 无内容
301 重定向 永久重定向
302 重定向 临时重定向
304 重定向 资源未修改,使用本地缓存
400 客户端错误 请求语法错误
401 客户端错误 未认证
403 客户端错误 禁止访问
404 客户端错误 资源不存在
500 服务端错误 服务器内部错误
502 服务端错误 网关错误
503 服务端错误 服务不可用
504 服务端错误 网关超时

七、浏览器解析与渲染页面

7.1 渲染流程总览

浏览器渲染引擎(如 Blink、WebKit)拿到 HTML 后,会经过以下关键步骤:

HTML  ──解析──►  DOM 树
                  │
CSS   ──解析──►  CSSOM 树
                  │
                  ▼
               Render 树(渲染树)
                  │
                  ▼
              Layout(布局/回流)
                  │
                  ▼
              Paint(绘制)
                  │
                  ▼
              Composite(合成)
                  │
                  ▼
              显示到屏幕

7.2 关键步骤详解

1. 构建 DOM 树

浏览器按字节 → 字符 → Token → Node → DOM Tree 的顺序解析 HTML。

<!-- 这段 HTML 会被解析为对应的 DOM 树 -->
<html>
  <head><title>Demo</title></head>
  <body>
    <p>Hello</p>
  </body>
</html>

2. 构建 CSSOM 树

CSS 解析过程中,浏览器会阻塞渲染,因为 CSS 影响元素样式。

3. 构建 Render 树

将 DOM 和 CSSOM 合并,不可见的元素(如 display:none、<head>)不会进入渲染树。

4. Layout(布局/回流 Reflow)

计算每个节点在屏幕上的精确位置和大小。

5. Paint(绘制 Repaint)

将每个节点绘制成像素。

6. Composite(合成)

将多个图层合并成最终页面,由 GPU 加速完成。

7.3 回流(Reflow)与重绘(Repaint)

对比项 回流 Reflow 重绘 Repaint
触发条件 元素几何属性变化(宽高、位置等) 元素外观变化(颜色、背景)
是否影响布局 ✅ ❌
性能开销 大 小
是否触发重绘 ✅ 一定会 ❌

🚨 警告: 回流必然引起重绘,但重绘不一定引起回流。频繁回流是性能优化的重点。

减少回流/重绘的方法:

// ❌ 多次操作 DOM,触发多次回流
const list = document.getElementById('list')
for (let i = 0; i < 100; i++) {
  list.innerHTML += `<li>${i}</li>`  // 每次都会触发回流
}

// ✅ 使用 DocumentFragment 批量操作
const fragment = document.createDocumentFragment()
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li')
  li.textContent = i
  fragment.appendChild(li)  // 仅在内存中操作
}
list.appendChild(fragment)  // 只触发一次回流
// ✅ 使用 CSS class 替代多次修改 style
// 推荐写法:合并样式变更
element.classList.add('active')  // 一次回流

// ❌ 不推荐:多次修改 style
element.style.width = '100px'    // 触发回流
element.style.height = '100px'   // 再次触发回流
element.style.margin = '10px'    // 又一次触发回流

八、JS 解析与执行

8.1 JS 解析的整体流程

JS 源代码
   ↓
词法分析(Tokenizing/Lexing)
   ↓
语法分析(Parsing)→ 生成 AST(抽象语法树)
   ↓
预解析(变量提升、函数提升)
   ↓
字节码生成
   ↓
解释执行 / JIT 编译为机器码

8.2 预解析(变量提升)

JS 引擎在执行代码前会先扫描整个作用域,将 变量声明和函数声明提升到顶部。

// 原始代码
console.log(a)   // 输出 undefined(不是报错)
console.log(b)   // 输出 ƒ b() {}(函数被完整提升)
var a = 1
function b() {}

// 等价于经过预解析后的代码
var a              // 变量声明被提升,但未赋值
function b() {}    // 函数声明被整体提升
console.log(a)     // undefined
console.log(b)     // ƒ b() {}
a = 1              // 赋值留在原位置

⚠️ 注意: let 和 const 不会被提升,存在"暂时性死区"(TDZ),在声明前访问会抛出 ReferenceError。

// let / const 的暂时性死区
console.log(x)     // ❌ ReferenceError: Cannot access 'x' before initialization
let x = 10

// var 的变量提升
console.log(y)     // ✅ undefined
var y = 10

8.3 浏览器引擎执行 JS

主流 JS 引擎:

引擎 浏览器
V8 Chrome、Edge、Node.js
SpiderMonkey Firefox
JavaScriptCore Safari

V8 执行流程:

源代码
  ↓
Parser(解析)→ AST
  ↓
Ignition(解释器)→ 字节码 → 立即执行
  ↓                          ↓
  ↓                  收集运行时反馈
  ↓                          ↓
TurboFan(优化编译器)← 热点代码标记
  ↓
高度优化的机器码

8.4 JS 与页面渲染的关系

JS 是单线程的,且会阻塞 DOM 解析。

<!-- 默认行为:阻塞 DOM 解析,立即下载并执行 -->
<script src="app.js"></script>

<!-- async:异步下载,下载完成后立即执行(仍会阻塞解析) -->
<script async src="app.js"></script>

<!-- defer:异步下载,等 HTML 解析完成后再执行 -->
<script defer src="app.js"></script>

三者对比:

属性 下载 执行时机 是否阻塞 HTML 解析 执行顺序
无属性 同步 立即执行 ✅ 阻塞 按文档顺序
async 异步 下载完立即执行 执行时阻塞 不保证顺序
defer 异步 DOMContentLoaded 之前 ❌ 不阻塞 按文档顺序

💡 提示: 推荐将不影响首屏的脚本放到 body 底部,或使用 defer 属性,避免阻塞页面渲染。

8.5 完整渲染时间线示例

时间轴 ──────────────────────────────────────────────►

[HTML 下载] [解析 HTML 构建 DOM]
                  ↓
              遇到 <link rel="stylesheet">
                  ↓
              [CSS 下载] [构建 CSSOM]   ← 阻塞渲染
                  ↓
              遇到 <script>
                  ↓
              [JS 下载] [JS 执行]       ← 阻塞 DOM 解析
                  ↓
              继续解析 HTML
                  ↓
              [构建 Render 树]
                  ↓
              [Layout]
                  ↓
              [Paint]
                  ↓
              [Composite]
                  ↓
              DOMContentLoaded 事件触发
                  ↓
              [加载图片等外部资源]
                  ↓
              load 事件触发

九、性能优化要点总结

阶段 优化手段
网络 DNS Prefetch、Preconnect、CDN、HTTP/2、缓存策略
TCP Keep-Alive 复用连接、减少 HTTPS 握手开销
HTTP Gzip / Brotli 压缩、合并请求、雪碧图
HTML 减少 DOM 节点、合理嵌套
CSS 避免深层选择器、关键 CSS 内联、避免 @import
JS defer / async、代码分割、Tree Shaking、Web Worker
渲染 减少回流重绘、使用 transform / opacity 触发 GPU 加速
图片 懒加载、WebP/AVIF 格式、响应式图片

💡 最佳实践: 理解整个流程是性能优化的基础——只有知道时间花在哪里,才能针对性地优化。


十、面试高频问答

Q1:从输入 URL 到看到页面,发生了什么?

可以按照"URL 解析 → DNS → TCP → HTTPS → HTTP → 服务器处理 → 浏览器渲染 → 断开连接"这条主线展开回答,每一步都能再深入。

Q2:DNS 解析为什么用 UDP?

UDP 无连接、报文头小、速度快,适合短小的查询请求;数据量大时会切换到 TCP。

Q3:为什么 TCP 是三次握手而不是两次或四次?

两次无法确认客户端的接收能力,可能建立无效连接;四次则多余,第二次握手中 SYN 和 ACK 可以合并。

Q4:defer 和 async 有什么区别?

参考上文 8.4 节中的对比表格。

Q5:如何减少回流和重绘?

批量修改 DOM、使用 class 代替 style、脱离文档流(absolute / fixed)、利用 transform 触发 GPU 合成层。