浏览器加载网页的完整旅程: 从 URL 到用户界面

Published on
21 mins read

当你在浏览器地址栏中输入 https://coooder-blog.vercel.app/ 并按下回车键时,看似简单的操作触发了一个复杂的流程。从这一刻开始,浏览器将经历四个主要阶段:网络请求、服务器处理、资源解析、页面渲染,最终将远程服务器上的代码转换成你眼前生动的博客网页。


一、网络请求阶段:建立连接获取资源

这是整个流程的第一阶段,浏览器需要通过网络获取网页的基础资源。

1. URL 解析与导航决策

浏览器首先需要理解你输入的 URL,一个完整的 URL 通常包含以下部分:

https://www.example.com:443/path/to/page?query=value#fragment

 |       |              |   |           |            |
协议   主机名(域名)     端口  路径       查询参数      片段标识符

浏览器会进行以下解析:

  • 协议识别https:// 告诉浏览器使用安全的 HTTP 协议
  • 域名提取www.example.com 是需要解析的目标地址
  • 端口确定:HTTPS 默认使用 443 端口,HTTP 使用 80 端口
  • 路径解析:确定请求的具体资源路径

浏览器还会进行一系列安全和策略检查:

  • Same-Origin Policy:检查是否违反同源策略
  • Content Security Policy (CSP):验证内容安全策略
  • HSTS 检查:如果之前访问过该域名,检查是否强制 HTTPS

📌 现代浏览器会在你输入 URL 时就开始预解析 DNS,这叫做 DNS 预取(DNS Prefetch),为后续步骤节省时间。更加激进的策略还有 Preconnect,不仅解析 DNS,还提前建立 TCP 链接和 TLS 握手,但会消耗更多资源。

2. DNS 解析:从域名到 IP 地址

DNS 解析是一个递归查询过程,遵循严格的层次结构:

用户输入:
www.example.com


查询层次:
1. 浏览器 DNS 缓存
2. 操作系统 DNS 缓存
3. 路由器 DNS 缓存
4. ISP DNS 服务器
5. 根域名服务器 (.)
6. 顶级域名服务器 (.com)
7. 权威域名服务器 (
example.com
)

缓存机制的威力

  • 浏览器缓存:通常缓存几分钟到几小时
  • 系统缓存:由操作系统管理,生存时间更长
  • ISP 缓存:互联网服务提供商的缓存服务器

现代 DNS 相关优化

  • DNS over HTTPS (DoH):通过 HTTPS 加密 DNS 查询,提高隐私和安全性
  • DNS over TLS (DoT):另一种加密 DNS 查询的方式
  • DNS 预取<link rel="dns-prefetch" href="//example.com"> 提前解析域名

⏱️ DNS 解析通常需要 20–120 毫秒,但通过缓存机制,大部分查询可以在几毫秒内完成。

3. 建立网络连接

TCP 三次握手(及四次挥手)

在获得 IP 地址后,浏览器需要与服务器建立 TCP 连接:

客户端 → 服务器:SYN (同步序列号)
客户端 ← 服务器:SYN-ACK (同步-确认)
客户端 → 服务器:ACK (确认)

连接建立成功 ✅

TLS 握手过程

对于 HTTPS 连接,还需要额外的 TLS 握手:

  1. Client Hello:客户端发送支持的加密套件列表
  2. Server Hello:服务器选择加密套件并发送证书
  3. 证书验证:客户端验证服务器证书的有效性
  4. 密钥交换:使用非对称加密交换对称密钥
  5. 握手完成:双方确认加密通道建立

连接复用技术

  • HTTP/1.1 Keep-Alive:复用 TCP 连接处理多个请求
  • HTTP/2 多路复用:单个连接同时处理多个流
  • HTTP/3 QUIC:基于 UDP 的快速连接建立

🔒 虽然 TLS 握手会增加延迟,但现代优化(如 TLS 1.3)已将握手时间减少到 1–2 个 RTT。

4. 发送 HTTP 请求

浏览器构建一个详细的 HTTP 请求:

GET /index.html HTTP/1.1
Host:
www.example.com

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cache-Control: max-age=0

关键请求头解析

  • Host:指定目标服务器
  • User-Agent:浏览器标识信息
  • Accept:客户端可接受的内容类型
  • Accept-Encoding:支持的压缩算法
  • Cache-Control:缓存策略指令

常用请求方法

  • GET:获取资源(幂等、安全)
  • POST:提交数据(非幂等)
  • PUT:更新资源(幂等)
  • DELETE:删除资源(幂等)

二、服务器处理阶段:生成响应内容

服务器接收请求后,需要处理业务逻辑并生成响应。

1. 服务器处理流程

虽然这主要是后端的工作,但理解服务器处理过程有助于前端优化:

  1. 请求路由:根据 URL 路径确定处理器
  2. 身份验证:验证用户身份和权限
  3. 业务逻辑处理:执行应用程序代码
  4. 数据库查询:获取动态数据
  5. 模板渲染:生成最终 HTML
  6. 压缩优化:使用 Gzip / Brotli 压缩响应

2. 常见的响应状态码

2xx 成功状态
200 OK              - 请求成功
201 Created         - 资源已创建
204 No Content      - 成功但无内容

3xx 重定向状态
301 Moved Permanently - 永久重定向
302 Found             - 临时重定向
304 Not Modified      - 资源未修改

4xx 客户端错误
400 Bad Request     - 请求语法错误
401 Unauthorized    - 需要身份验证
402 Payment Required- 需要付款
403 Forbidden       - 禁止访问
404 Not Found       - 资源不存在

5xx 服务器错误
500 Internal Server Error - 服务器内部错误
502 Bad Gateway           - 网关错误
503 Service Unavailable   - 服务不可用

📌 402 一直没被广泛实际使用过,最近 Coinbase 提出的 x402 协议尝试把 402 与 Crypto 支付结合起来。

3. 接收和处理响应

服务器生成响应后,浏览器开始接收并处理响应数据。

响应头的重要信息

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 12345
Content-Encoding: gzip
Cache-Control: public, max-age=3600
Last-Modified: Wed, 15 Dec 2024 10:00:00 GMT
ETag: "abc123def456"
Set-Cookie: sessionid=abc123; HttpOnly; Secure

缓存策略处理

  • 强缓存:Cache-Control: max-age=3600 表示缓存 1 小时
  • 协商缓存:通过 ETagLast-Modified 验证资源是否更新
  • 缓存位置:内存缓存 → 磁盘缓存 → 推送缓存

流式处理

现代浏览器支持边接收边解析 HTML:

  • 边接收边解析:不等待完整响应即开始解析
  • 渐进式渲染:页面内容逐步显示
  • 优先级调度:关键资源优先处理

三、资源解析阶段:构建页面结构

浏览器接收到 HTML 响应后,开始解析并构建页面的基础结构。

1. HTML 解析与 DOM 构建

词法分析阶段

HTML 解析器将文本转换为 token:

<div class="container">Hello World</div>

// 转换为 tokens: // StartTag: div, attributes: [class="container"] //
Characters: "Hello World" // EndTag: div

构建 DOM 树

解析器使用栈结构维护元素的嵌套关系:

Document
  └── html
      ├── head
      │   ├── title
      │   └── meta
      └── body
          ├── div.container
          │   └── "Hello World"
          └── script

解析中断与恢复

当遇到阻塞资源时:

  • 同步脚本:暂停 DOM 构建,下载并执行脚本
  • 样式表:继续 DOM 构建,但可能阻塞渲染
  • async 脚本:异步下载,不阻塞 DOM 构建
  • defer 脚本:异步下载,DOM 完成后执行

📖 HTML 解析器是容错的,能处理格式不完整的 HTML,这也是许多“不规范”网页仍能正常显示的原因。

2. CSS 解析与样式计算

CSSOM 构建

CSS 解析器会构建样式对象模型(CSSOM):

body {
  font-size: 16px;
}
.container {
  width: 100%;
  padding: 20px;
}
.container p {
  color: #333;
  line-height: 1.5;
}

样式计算过程

  1. 样式收集:从多个来源收集样式规则
    • 浏览器默认样式
    • 用户代理样式表
    • 用户自定义样式
    • 网页 CSS 文件
  2. 选择器匹配:确定哪些规则适用于每个元素
  3. 优先级计算:根据 CSS 特殊性规则确定最终样式
/* 特殊性计算:内联样式 > ID > 类 / 属性 / 伪类 > 元素 / 伪元素 */
div {
  color: red;
} /* 特殊性: 0,0,0,1 */
.highlight {
  color: blue;
} /* 特殊性: 0,0,1,0 */
#main {
  color: green;
} /* 特殊性: 0,1,0,0 */
  1. 值计算:处理继承、初始值和计算值
    • 指定值:CSS 规则声明的值
    • 计算值:相对值转换为绝对值
    • 使用值:最终用于渲染的值

现代 CSS 优化

  • 选择器从右到左匹配
  • 样式缓存:避免重复计算
  • 增量更新:只重新计算受影响的元素

四、页面渲染阶段:生成最终界面

有了 DOM 树和样式信息后,浏览器开始布局和绘制,生成用户最终看到的界面。

1. 布局计算(Layout)

渲染树构建

将 DOM 树和 CSSOM 合并生成渲染树:

  • 只包含需要显示的元素(display: none 的元素被排除)
  • 每个节点包含样式信息
  • 保持文档顺序

布局算法

  1. 全局布局:从根元素开始计算
  2. 块级格式化上下文(BFC):处理块级元素布局
  3. 内联格式化上下文(IFC):处理内联元素布局
  4. 弹性布局(Flexbox):现代一维布局方案
  5. 网格布局(Grid):二维布局系统

几何属性计算

对每个元素,浏览器会计算类似如下的信息:

// 位置信息
x: 100, // 相对于父容器的 x 坐标
y: 50,  // 相对于父容器的 y 坐标

// 尺寸信息
width: 300,  // 内容宽度
height: 200, // 内容高度

// 盒模型
margin: { ... },
border: { ... },
padding: { ... },x w

布局优化策略

  • 部分重布局:只重新计算受影响的子树
  • 布局边界:某些 CSS 属性可以限制布局传播
  • 虚拟化:长列表只渲染可视区域内的元素

性能提示:频繁的布局计算是性能瓶颈,避免在循环中多次读取会触发布局的属性。

2. 绘制准备(Paint)

绘制层级划分

浏览器将页面分解为多个绘制层:

  • 普通文档流:大部分元素在同一层
  • 层叠上下文:满足以下条件的元素会创建新层级,例如:
    • z-index 不为 auto 的定位元素
    • opacity 小于 1 的元素
    • 使用 transform 的元素
    • position: fixed 的元素

绘制指令生成

对每个图层生成绘制指令列表,例如:

[
  { type: "fillRect", color: "#ffffff", x: 0, y: 0, width: 300, height: 200 },
  {
    type: "drawText",
    text: "Hello",
    font: "16px Arial",
    color: "#000",
    x: 10,
    y: 30,
  },
  {
    type: "strokeRect",
    color: "#cccccc",
    x: 0,
    y: 0,
    width: 300,
    height: 200,
    lineWidth: 1,
  },
];

绘制优化

  • 脏区域标记:只重绘发生变化的区域
  • 绘制指令缓存:避免重复生成
  • 图层合并:在合适时机合并图层以减少内存使用

3. 合成与光栅化

图块(Tile)分割

大的绘制层被分割成 256×256 或 512×512 的图块:

页面层级 (1920x1080)
├── Tile[0,0] (256x256)
├── Tile[1,0] (256x256)
├── Tile[0,1] (256x256)
└── ...(其他图块)

光栅化过程

  1. 优先级排序:视口内的图块优先处理
  2. 工作线程池:多个光栅化线程并行工作
  3. GPU 加速:利用显卡加速绘制
  4. 位图生成:将矢量指令转换为像素数据

合成器(Compositor)职责

  • 图层管理:跟踪所有图层状态
  • 视口裁剪:只处理可见区域图块
  • 变换应用:应用 CSS transform 等效果
  • 最终合成:将所有图层合成为最终图像

🖥️ 现代浏览器会尽可能使用 GPU 进行合成,对动画和滚动尤为重要。

4. 显示与交互准备

帧缓冲与显示

最终图像数据被写入帧缓冲区:

帧缓冲区 → 显卡 → 显示器

刷新率示例:
- 60 fps  ≈ 16.67 ms/- 120 fps ≈ 8.33 ms/- 144 fps ≈ 6.94 ms/

JavaScript 执行与事件循环

一旦 DOM 构建完成,就会开始执行 JavaScript:

  1. 脚本加载顺序:
<script src="sync.js"></script>
<!-- 同步执行 -->
<script src="async.js" async></script>
<!-- 异步执行 -->
<script src="defer.js" defer></script>
<!-- 延迟执行 -->
  1. 事件循环:
    • 调用栈:执行 JavaScript 代码
    • 任务队列:处理异步任务
    • 微任务队列:处理 Promise 等高优先级任务
  2. 事件监听器绑定示例:
// DOMContentLoaded:DOM 构建完成

JavaScript 执行

一旦 DOM 构建完成,浏览器就会开始执行 JavaScript,并启动事件循环。

  1. 脚本加载顺序
<script src="sync.js"></script>
<!-- 同步执行:阻塞解析,按顺序执行 -->

<script src="async.js" async></script>
<!-- 异步执行:下载与解析并行,下载完立刻执行,不保证顺序 -->

<script src="defer.js" defer></script>
<!-- 延迟执行:下载与解析并行,DOM 完成后按顺序执行 -->
  1. 事件循环启动
  • 调用栈(Call Stack):执行 JavaScript 代码
  • 任务队列(Task Queue / Macro Task Queue):处理 setTimeoutsetInterval、事件回调等异步任务
  • 微任务队列(Microtask Queue):处理 Promise.thenMutationObserver 等高优先级任务

浏览器在每一轮事件循环中大致会:

  1. 取出一个宏任务执行完毕
  2. 清空当前产生的所有微任务
  3. 进行必要的渲染更新
  4. 事件监听器绑定示例
// DOMContentLoaded:DOM 构建完成
// 适合只依赖 DOM、不依赖图片等资源的初始化逻辑

document.addEventListener("DOMContentLoaded", function () {
  console.log("DOM 已准备就绪");
});

// load:所有资源(图片、样式表等)加载完成
// 适合依赖完整资源的逻辑

window.addEventListener("load", function () {
  console.log("页面完全加载完成");
});

用户交互就绪

当关键脚本执行完毕、事件监听器绑定完成后,页面进入可交互状态:

  • 可点击元素:按钮、链接等可以响应用户操作
  • 表单可用:输入框可以正常接受用户输入
  • 滚动流畅:页面支持平滑滚动和惯性滚动
  • 动画开始:CSS 动画和 JavaScript 驱动的动画开始执行

现代浏览器的性能优化

预加载技术

<!-- DNS 预解析 -->
<link
  rel="dns-prefetch"
  href="//
example.com
"
/>

<!-- 预连接 -->
<link
  rel="preconnect"
  href="
https://fonts.googleapis.com
"
/>

<!-- 资源预加载 -->
<link rel="preload" href="critical.css" as="style" />
<link rel="preload" href="hero.jpg" as="image" />

<!-- 页面预取(适合预测用户下一步可能访问的页面) -->
<link rel="prefetch" href="/next-page.html" />

关键渲染路径优化

  1. 最小化关键资源
  • 减少阻塞渲染的 CSS 文件
  • 内联关键 CSS
  • 异步加载非关键 JavaScript
  1. 减少关键字节数
  • 压缩 HTML、CSS、JavaScript
  • 移除未使用的代码
  • 使用现代图片格式 (WebP、AVIF)
  1. 缩短关键路径长度
  • 减少重定向
  • 避免 render-blocking 资源
  • 优化资源加载顺序

现代 Web 标准

  • HTTP/3:基于 QUIC 协议,减少连接建立延迟
  • Server Push(HTTP/2):服务器可主动推送关键资源(虽然现在使用减少,但原理仍然重要)
  • Service Worker:离线缓存和后台任务处理,支持 PWA 能力
  • WebAssembly:适合高性能计算场景
  • Progressive Web App (PWA):提供接近原生应用的体验(离线、安装、通知等)

性能测量与监控

Core Web Vitals

Google 提出的三个核心指标:

  1. LCP (Largest Contentful Paint):最大内容绘制时间
    • 理想值:< 2.5 秒
    • 关注首屏主要内容(如 Hero 图、主标题)何时完成渲染
  2. FID (First Input Delay):首次输入延迟
    • 理想值:< 100 毫秒
    • 测量用户首次交互(点击、输入)到浏览器开始处理该事件之间的延迟
  3. CLS (Cumulative Layout Shift):累积布局位移
    • 理想值:< 0.1
    • 衡量页面视觉稳定性,防止“内容突然跳动”

其他重要指标

  • FCP (First Contentful Paint):首次内容绘制
  • TTI (Time to Interactive):页面真正可交互的时间
  • TTFB (Time to First Byte):首字节时间,反映后端和网络延迟
  • Speed Index:页面可见内容加载速度的综合评分

性能测试工具示例

// 使用 Performance API 监听关键性能条目
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, entry.startTime, entry.duration);
  }
});

observer.observe({ entryTypes: ["navigation", "resource", "paint"] });

// 测量自定义功能耗时
performance.mark("feature-start");
// ... 执行某些操作
performance.mark("feature-end");
performance.measure("feature-duration", "feature-start", "feature-end");

总结

以上整个过程看似复杂,但现代浏览器通过预加载、缓存、多进程 / 多线程、GPU 合成等优化手段,把用户的等待压缩到秒级甚至毫秒级。

从 URL 输入到页面完全可交互,这条“关键路径”是前端性能优化的主战场。理解这些细节,有助于我们在遇到性能瓶颈时更快地定位问题,并做出有针对性的优化。