mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1155 字
3 分钟
浏览器渲染原理:从 URL 到页面
2023-04-09

前言#

“从输入 URL 到页面展示发生了什么?” 是前端面试中最经典的问题之一。这个问题看似简单,实则涉及网络协议、浏览器架构、渲染引擎等多个领域的知识。本文将系统性地剖析整个流程,让你对浏览器工作原理有深入理解。

全流程概览#

flowchart TB subgraph 网络层 A[URL 解析] --> B[DNS 查询] B --> C[TCP 连接] C --> D[TLS 握手] D --> E[HTTP 请求] E --> F[接收响应] end subgraph 解析层 G[HTML 解析] --> H[构建 DOM 树] H --> I[CSS 解析] I --> J[构建 CSSOM 树] J --> K[执行 JavaScript] end subgraph 渲染层 L[构建渲染树] --> M[布局计算] M --> N[绘制] N --> O[合成] O --> P[显示] end F --> G K -.-> H

一、网络层#

1.1 URL 解析#

浏览器首先对 URL 进行解析:

https://www.example.com:443/path/to/page?query=value#hash
└─┬──┘ └───────┬───────┘ ┊ ┊ └─────┬─────┘ └───┬───┘
协议 域名 端口 路径 查询参数 锚点
组件示例说明
协议https决定使用 HTTP 还是 HTTPS
域名www.example.com需要解析为 IP 地址
端口443HTTP 默认 80,HTTPS 默认 443
路径/path/to/page请求资源路径
查询?query=value传递参数
锚点#hash页面内定位

1.2 DNS 查询#

DNS 解析将域名转换为 IP 地址:

sequenceDiagram participant B as 浏览器缓存 participant S as 系统缓存 participant R as 路由器缓存 participant L as 本地 DNS participant R as 根 DNS participant A as 权威 DNS Note over B: 检查浏览器缓存 B-->>B: 未命中 Note over S: 检查系统缓存 S-->>S: 未命中 S->>L: 递归查询请求 L->>R: 查询 .com NS R-->>L: 返回 .com NS 地址 L->>A: 查询 example.com A-->>L: 返回 IP 地址 L-->>S: 返回 IP 地址

DNS 缓存层级

缓存位置TTL说明
浏览器缓存1-60 秒最快,容量有限
系统缓存根据 DNS 设置OS 级别缓存
路由器缓存不定家用路由器
ISP DNS根据 DNS 设置运营商缓存

1.3 TCP 连接#

建立 TCP 连接需要三次握手:

sequenceDiagram participant C as 客户端 participant S as 服务端 Note over C: SYN_SENT C->>S: SYN=1, seq=x Note over S: SYN_RCVD S->>C: SYN=1, ACK=1, seq=y, ack=x+1 Note over C: ESTABLISHED C->>S: ACK=1, seq=x+1, ack=y+1 Note over S: ESTABLISHED
状态说明
SYN_SENT发送 SYN 后等待
SYN_RCVD收到 SYN,发送 SYN+ACK
ESTABLISHED连接建立完成

TCP Fast Open (TFO)

现代浏览器支持 TCP Fast Open,可以在第三次握手时携带数据:

首次连接:正常三次握手,获取 Cookie
后续连接:SYN + Cookie + 数据 → 跳过等待

1.4 TLS 握手#

HTTPS 连接需要 TLS 握手(以 TLS 1.3 为例):

sequenceDiagram participant C as Client participant S as Server C->>S: ClientHello + Key Share Note over C: 计算共享密钥 S->>C: ServerHello + Key Share + Certificate + Finished Note over S: 计算共享密钥 C->>S: Finished Note over C,S: 加密通信开始

TLS 1.3 改进

特性TLS 1.2TLS 1.3
握手 RTT21
0-RTT不支持支持
密码套件多种仅 AEAD
前向保密可选强制

1.5 HTTP 请求#

连接建立后发送 HTTP 请求:

GET /path/to/page HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 ...
Accept: text/html,application/xhtml+xml
Accept-Language: zh-CN,zh;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session_id=abc123

HTTP/2 多路复用

flowchart LR subgraph HTTP/1.1 R1[请求1] --> R2[请求2] R2 --> R3[请求3] end subgraph HTTP/2 S1[流1] S2[流2] S3[流3] S1 -.-> S2 -.-> S3 end

1.6 接收响应#

服务器返回响应:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Cache-Control: max-age=3600
Transfer-Encoding: chunked
<!DOCTYPE html>
<html>
...

二、解析层#

2.1 HTML 解析#

浏览器使用 HTML 解析器将字节流转换为 DOM 树:

flowchart LR A[字节流] --> B[字符流] B --> C[词法分析 Token] C --> D[语法分析] D --> E[DOM 树]

解析过程

输入: <div id="app">Hello</div>
1. 词法分析生成 Token:
- StartTag: div, attributes: {id: "app"}
- Character: "Hello"
- EndTag: div
2. 语法分析构建树:
Document
└── HTMLHtmlElement
└── HTMLBodyElement
└── HTMLDivElement (id="app")
└── Text: "Hello"

预扫描优化

浏览器会预扫描 HTML,提前发现需要加载的资源:

<html>
<head>
<!-- 预扫描发现 stylesheet,提前发起请求 -->
<link rel="stylesheet" href="style.css" />
<!-- 预扫描发现 script,但会等待 HTML 解析完 -->
<script src="app.js"></script>
</head>
...
</html>

2.2 阻塞与异步#

不同资源对解析的影响:

资源类型是否阻塞解析是否阻塞渲染
CSS
同步 JS
async JS
defer JS
图片
<!-- 阻塞解析 -->
<script src="blocking.js"></script>
<!-- 异步加载,不阻塞解析 -->
<script async src="analytics.js"></script>
<!-- 延迟执行,HTML 解析完后执行 -->
<script defer src="app.js"></script>
<!-- CSS 阻塞渲染 -->
<link rel="stylesheet" href="style.css" />

2.3 构建 DOM 树#

DOM 树的构建过程:

// 简化的 DOM 树结构
const domTree = {
nodeType: 9, // Document
childNodes: [
{
nodeType: 1, // Element
nodeName: 'HTML',
childNodes: [
{
nodeType: 1,
nodeName: 'HEAD',
childNodes: [...]
},
{
nodeType: 1,
nodeName: 'BODY',
childNodes: [...]
}
]
}
]
};

2.4 构建 CSSOM 树#

CSS 解析为 CSSOM(CSS Object Model):

/* 输入 CSS */
body {
font-size: 16px;
}
.container {
width: 100%;
}
.item {
color: red;
}
CSSOM 树:
body
├── font-size: 16px
└── .container
├── width: 100%
└── .item
└── color: red

CSS 继承与层叠

flowchart TB A[样式声明] --> B{来源} B -->|用户代理| C[优先级最低] B -->|用户样式| D[优先级中等] B -->|作者样式| E[优先级最高] E --> F{重要性} F -->|!important| G[最高优先级] F -->|普通声明| H{特异性计算] H --> I[ID 选择器: 100] H --> J[类选择器: 10] H --> K[元素选择器: 1]

2.5 执行 JavaScript#

JavaScript 执行流程:

flowchart TB A[下载 JS] --> B[解析] B --> C[编译] C --> D[执行] D --> E{操作} E -->|document.write| F[阻塞解析] E -->|DOM API| G[修改 DOM] E -->|CSSOM API| H[修改样式]

三、渲染层#

3.1 构建渲染树#

渲染树(Render Tree)是 DOM 和 CSSOM 的合并:

flowchart TB subgraph 输入 A[DOM 树] B[CSSOM 树] end subgraph 处理 C[合并] --> D[计算可见性] D --> E[计算样式] end subgraph 输出 F[渲染树] end A --> C B --> C E --> F

渲染树特点

  • 不包含 display: none 的元素
  • 包含 visibility: hidden 的元素
  • 不包含 <head> 等非可视元素
DOM 树: 渲染树:
<html> <html>
<head>...</head> <body>
<body> <div>
<div>可见</div> 可见
<div style="display:none"> <span>可见</span>
隐藏 </div>
</div> </body>
<span>可见</span> </html>
</body>
</html>

3.2 布局计算(Layout/Reflow)#

布局计算确定每个元素的位置和大小:

flowchart TB A[视口大小] --> B[根元素布局] B --> C[块级元素] C --> D[行内元素] D --> E[计算位置] E --> F[计算尺寸] F --> G[写入布局树]

布局计算过程

  1. 确定视口大小
  2. 从根元素开始递归
  3. 计算每个元素的盒子模型
  4. 处理浮动、定位、Flex/Grid
// 盒子模型计算
const box = {
content: { width: 100, height: 50 },
padding: { top: 10, right: 10, bottom: 10, left: 10 },
border: { top: 1, right: 1, bottom: 1, left: 1 },
margin: { top: 20, right: 0, bottom: 20, left: 0 },
};
// 总宽度 = content + padding + border
// 100 + 10*2 + 1*2 = 122px

触发 Reflow 的操作

操作影响
窗口大小变化全局 reflow
字体变化全局 reflow
添加/删除元素局部 reflow
改变尺寸/位置局部 reflow

3.3 绘制(Paint)#

绘制是将渲染树转换为像素的过程:

flowchart LR A[渲染树] --> B[绘制列表] B --> C[光栅化] C --> D[位图] D --> E[显示]

绘制顺序

1. 背景色
2. 背景图片
3. 边框
4. 子元素
5. 轮廓

触发 Repaint 的操作

  • 改变颜色
  • 改变背景
  • 改变 visibility
  • 改变阴影

3.4 合成(Composite)#

现代浏览器使用合成层优化渲染:

flowchart TB subgraph 主线程 A[DOM/CSSOM] --> B[布局] B --> C[绘制列表] end subgraph 合成线程 D[分块] --> E[光栅化] E --> F[合成] end subgraph GPU G[纹理上传] --> H[合成显示] end C --> D F --> G

提升为合成层的条件

CSS 属性效果
transform创建合成层
opacity创建合成层
will-change提示创建合成层
position: fixed可能创建合成层
/* 优化动画性能 */
.animated-element {
will-change: transform, opacity;
transform: translateZ(0); /* 强制硬件加速 */
}

四、关键渲染路径优化#

4.1 CRP 指标#

timeline title 关键渲染路径时间线 section 阻塞资源 DNS/TCP/TLS : 连接时间 HTML 下载 : 资源大小 CSS 下载 : 渲染阻塞 JS 执行 : 解析阻塞 section 渲染 DOM 构建 : 首次绘制 CSSOM : 首次内容绘制 布局/绘制 : 可交互时间
指标英文说明
FPFirst Paint首次绘制
FCPFirst Contentful Paint首次内容绘制
LCPLargest Contentful Paint最大内容绘制
TTITime to Interactive可交互时间
CLSCumulative Layout Shift累积布局偏移

4.2 优化策略#

CSS 优化

<!-- 内联关键 CSS -->
<head>
<style>
/* 首屏关键样式 */
.header { ... }
.hero { ... }
</style>
<!-- 异步加载非关键 CSS -->
<link
rel="preload"
href="main.css"
as="style"
onload="this.rel='stylesheet'"
/>
</head>

JavaScript 优化

<!-- 关键 JS 内联 -->
<script>
// 初始化代码
</script>
<!-- 非关键 JS 延迟加载 -->
<script defer src="analytics.js"></script>
<script async src="ads.js"></script>
<!-- 动态加载 -->
<script>
window.addEventListener("load", function () {
var script = document.createElement("script");
script.src = "non-critical.js";
document.body.appendChild(script);
});
</script>

资源预加载

<!-- 预连接 -->
<link rel="preconnect" href="https://cdn.example.com" />
<link rel="dns-prefetch" href="https://analytics.example.com" />
<!-- 预加载关键资源 -->
<link rel="preload" href="critical-font.woff2" as="font" crossorigin />
<link rel="preload" href="hero-image.jpg" as="image" />
<!-- 预获取未来资源 -->
<link rel="prefetch" href="next-page.html" />

五、浏览器架构#

5.1 多进程架构#

现代浏览器的多进程架构:

flowchart TB subgraph Browser Process A[主进程] --> B[UI 线程] A --> C[网络线程] A --> D[存储线程] end subgraph Renderer Processes E[渲染进程 1] F[渲染进程 2] end subgraph Utility Processes G[GPU 进程] H[插件进程] I[扩展进程] end A <--> E A <--> F A <--> G
进程职责
BrowserUI、网络、存储、协调
Renderer页面渲染、JavaScript 执行
GPU图形渲染、合成
Plugin插件运行
Utility实用工具(如网络服务)

5.2 渲染进程组成#

flowchart TB subgraph 渲染进程 A[主线程] B[Worker 线程] C[合成线程] D[光栅线程] end A --> |HTML/CSS| E[DOM/CSSOM] A --> |布局| F[Render Tree] A --> |绘制| G[Paint Records] G --> C C --> D D --> |位图| H[显示]
线程职责
MainDOM、CSSOM、布局、绘制、JS
WorkerWeb Worker、Service Worker
Compositor合成层管理、滚动处理
Raster光栅化、位图生成

六、总结#

完整流程图#

flowchart TB subgraph 用户操作 A[输入 URL] end subgraph 网络层 B[URL 解析] --> C[DNS 查询] C --> D[TCP 三次握手] D --> E[TLS 握手] E --> F[HTTP 请求] F --> G[接收响应] end subgraph 解析层 H[HTML 解析] --> I[DOM 树] J[CSS 解析] --> K[CSSOM 树] L[JS 执行] end subgraph 渲染层 M[渲染树] --> N[布局] N --> O[绘制] O --> P[合成] P --> Q[显示] end A --> B G --> H G --> J I --> M K --> M L -.-> I

关键要点#

  1. 网络层:DNS、TCP、TLS、HTTP 是基础
  2. 解析层:DOM、CSSOM 构建可能被 JS 阻塞
  3. 渲染层:布局、绘制、合成是渲染三阶段
  4. 优化核心:减少关键资源、缩短 CRP 长度

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

浏览器渲染原理:从 URL 到页面
https://blog.souloss.com/posts/principles/browser-rendering-principles/
作者
Souloss
发布于
2023-04-09
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时