从输入 URL 到页面渲染:深入浏览器工作全过程(含缓存机制详解)
当你在浏览器地址栏输入一个 URL,比如 https://www.baidu.com,按下回车后,页面几乎瞬间加载完成。这个看似简单的过程背后,是浏览器、操作系统、网络协议以及服务器之间复杂而高效的协同工作。本文将带你系统性地梳理从输入 URL 到页面最终渲染的完整流程,并重点剖析其中的缓存机制,帮助你构建完整的前端知识体系。
一、浏览器多进程架构:一切的前提
现代浏览器(如 Chrome)采用多进程多线程架构,这是理解整个流程的基础。
主要进程包括:
| 进程 | 职责 |
|---|---|
| 浏览器主进程(Browser Process) | 控制 UI、地址栏、书签、网络请求、文件访问等 |
| 渲染进程(Renderer Process) | 负责解析 HTML、CSS,执行 JavaScript,构建 DOM 树、布局、绘制等 |
| GPU 进程(GPU Process) | 处理 GPU 相关任务,实现硬件加速渲染 |
| 网络进程(Network Process) | 管理网络请求、DNS 解析、资源缓存等 |
| 插件进程(Plugin Process) | 隔离运行第三方插件(如 Flash) |
✅ 关键点:每个标签页通常运行在一个独立的渲染进程中,实现进程隔离,防止一个页面崩溃影响整个浏览器。
二、输入 URL 到发起请求:解析与构造
1. 输入内容的识别
当你输入 baidu.com 时,浏览器首先判断这是一个结构化字符串(包含 .com 域名后缀),而不是搜索关键词。
- 若输入
如何学习前端→ 浏览器会将其作为关键词发送给默认搜索引擎。 - 若输入
baidu.com→ 浏览器识别为 URL,进行补全和解析。
2. 补全 URL
浏览器会自动补全协议和端口:
输入:baidu.com
补全为:https://www.baidu.com:443/
🔍 注意:
- HTTP 默认端口:80
- HTTPS 默认端口:443
- 其他常见端口:MySQL(3306)、Redis(6379)、MongoDB(27017)
如果用户输入的是 http://baidu.com,由于百度强制 HTTPS,服务器会返回 307 临时重定向:
HTTP/1.1 307 Temporary Redirect
Location: https://www.baidu.com/
3. URL 结构解析
一个完整的 URL 包含以下部分:
协议://域名:端口/路径?查询参数#哈希
https://www.baidu.com:443/search?q=前端#result
浏览器从中提取:
- 协议(Protocol):
https - 主机名(Host):
www.baidu.com - 端口(Port):
443 - 路径(Path):
/search - 查询参数(Query String):
q=前端 - 哈希(Hash):
result
三、DNS 解析:域名 → IP 地址
浏览器无法直接通过域名通信,必须通过 DNS(Domain Name System) 将域名解析为 IP 地址。 详细介绍可以看从输入URL到网页显示,背后隐藏的分布式电话簿DNS这篇文章
DNS 查询流程:
- 检查本地 Hosts 文件
- 查询本地 DNS 缓存(浏览器、操作系统)
- 向配置的 DNS 服务器发起请求(递归查询)
- DNS 服务器进行迭代查询(根 → 顶级域 → 权威 DNS)
- 返回 IP 地址(如
14.215.177.39)
✅ 优化手段:预解析(
<link rel="dns-prefetch" href="//www.baidu.com">)
四、建立 TCP 连接与 TLS 握手
当我们输入一个 HTTPS 网址后,在发送 HTTP 请求之前,浏览器需要先与服务器建立可靠的、加密的通信通道。这个过程分为两个阶段:TCP 三次握手建立连接,然后是 TLS/SSL 握手实现安全传输。
1. TCP 三次握手
获取 IP 后,浏览器通过 TCP 协议与服务器的 443 端口建立连接:
Client → SYN → Server
Client ← SYN-ACK ← Server
Client → ACK → Server
💡 目的:确保客户端和服务器之间建立一条可靠的双向通信通道。
整个过程就像两个人打电话确认是否能正常通话:
- 第一次握手(SYN)
客户端发送一个SYN=1的报文,并携带一个随机生成的序列号seq=x,表示:“我想和你建立连接”。
→ 此时客户端进入SYN_SENT状态。 - 第二次握手(SYN+ACK)
服务器收到后,回应一个SYN=1, ACK=1的报文,确认客户端的 SYN,并带上自己的初始序列号seq=y,同时对客户端的x进行确认ack=x+1。
→ 表示:“我收到了你的请求,我也准备好连接了。”
→ 此时服务器进入SYN_RECEIVED状态。 - 第三次握手(ACK)
客户端收到后,再发送一个ACK=1的报文,确认服务器的y,即ack=y+1。
→ 表示:“我也收到你的回复了,连接可以开始了。”
→ 双方进入ESTABLISHED状态,连接建立成功。
✅ 为什么是三次?不能两次吗?
因为 TCP 是全双工通信,需要双方都确认“我能发,也能收”。
如果只有两次,服务器无法确认客户端是否收到了自己的响应(比如 SYN 丢了),可能导致“半开连接”或资源浪费。
2. TLS/SSL 加密握手(HTTPS)
为了安全传输,还需进行 TLS 握手:
- 客户端发送支持的加密套件
- 服务器返回证书(含公钥)
- 验证证书有效性(CA 签发、有效期、域名匹配)
- 双方协商生成“会话密钥”
- 后续通信使用对称加密
🔐 安全提示:浏览器会检查证书是否可信,否则提示“不安全”。 💡 目的:在已建立的 TCP 连接上,协商加密算法、验证身份、生成会话密钥,实现数据加密、防篡改、身份认证。
以最常见的 TLS 1.2 为例:
-
Client Hello
客户端发送支持的 TLS 版本、加密套件列表(如 RSA、ECDHE)、随机数Client Random。 -
Server Hello
服务器选择一个双方都支持的加密套件,并返回:- 选定的 TLS 版本和加密套件
- 服务器随机数
Server Random - 数字证书(包含公钥、域名、CA 签发信息)
-
证书验证
客户端验证证书:- 是否由可信 CA 签发
- 域名是否匹配
- 是否在有效期内
❌ 如果验证失败,浏览器会弹出“不安全”警告。
-
密钥交换(以 RSA 为例)
- 客户端生成一个预主密钥(Pre-Master Secret)
- 用服务器证书中的公钥加密后发送给服务器
-
生成会话密钥
- 双方使用
Client Random + Server Random + Pre-Master Secret通过算法生成相同的会话密钥(Session Key) - 后续通信使用这个对称加密密钥(因为对称加密效率高)
- 双方使用
-
Finished 消息
- 双方发送加密的
Finished消息,验证握手是否成功 - 握手完成,开始使用对称加密传输数据
- 双方发送加密的
✅ 为什么用“混合加密”?
- 非对称加密(如 RSA)用于安全地传输“会话密钥”,解决密钥分发问题
- 对称加密(如 AES)用于加密实际数据,性能更高
📌 总结:一张表看懂所有术语
| 名称 | 所属协议 | 类型 | 作用 |
|---|---|---|---|
SYN | TCP | 标志位 | 发起连接请求,同步序列号 |
ACK | TCP | 标志位 | 确认收到数据或连接请求 |
Client Random | TLS | 随机数 | 参与生成会话密钥,增加随机性 |
Server Random | TLS | 随机数 | 同上 |
Certificate | TLS | 数字证书 | 验证服务器身份,提供公钥 |
Pre-Master Secret | TLS | 秘密随机数 | 客户端生成,用公钥加密传输,用于生成会话密钥 |
💡 小贴士:如何记住?
你可以想象这是一个“安全建联”的过程:
- 打电话接通 → TCP 三次握手(SYN, ACK)
- 确认对方身份 → 检查 Certificate
- 商量一个只有你们知道的密码 → 用 Random + Pre-Master Secret 生成 Session Key
一旦这个过程完成,你们就可以用这个“密码”安全地聊天了(加密传输 HTTP 数据)。
五、发送 HTTP 请求
连接建立后,浏览器构造并发送 HTTP 请求:
GET /search?q=前端 HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
User-Agent: Mozilla/5.0...
Accept: text/html,application/xhtml+xml...
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: BAIDUID=xxxx; BIDUPSID=yyyy
⚠️ 注意:Cookie 会自动附加在请求头中,用于身份识别。
一个完整的 HTTP 请求由以下四个部分组成:
| 部分 | 说明 |
|---|---|
| 1. 请求行(Request Line) | 包含方法、路径、协议版本 |
| 2. 请求头(Request Headers) | 键值对,描述客户端信息、偏好、认证等 |
| 3. 空行(Empty Line) | 分隔头部和主体,必须存在 |
| 4. 请求体(Request Body) | 可选,用于 POST、PUT 等携带数据 |
📌 示例结构:
POST /login HTTP/1.1 ← 请求行
Host: www.baidu.com ← 请求头
Content-Type: application/json
Content-Length: 27
Cookie: session=abc123
{"username":"admin","pwd":"123"} ← 请求体(空行之后)
请求行包含什么?
请求行是 HTTP 请求的第一行,格式为:
[HTTP方法] [请求路径]?[查询参数] [HTTP版本]
以 GET /search?q=前端 HTTP/1.1 为例:
- HTTP 方法:
GET(获取资源),其他还有POST、PUT、DELETE等 - 请求路径:
/search,表示服务器上的资源路径 - 查询参数(Query String) :
q=前端,用于传递简单参数(URL 编码) - HTTP 版本:
HTTP/1.1,表示使用的协议版本
📌 补充:路径和查询参数合起来叫 URI(Uniform Resource Identifier)
常见的请求头有哪些?作用是什么?
🎯 推荐回答方式:分类 + 举例 + 作用
| 请求头 | 类别 | 作用说明 |
|---|---|---|
Host | 基础 | 指定目标主机和端口。必须字段(HTTP/1.1),支持虚拟主机(一台服务器托管多个网站) |
Connection | 控制 | keep-alive 表示复用 TCP 连接,提升性能;close 表示用完关闭 |
User-Agent | 客户端信息 | 告诉服务器客户端类型(浏览器、操作系统),用于兼容性判断或统计 |
Accept | 内容协商 | 告诉服务器“我能接受哪些 MIME 类型”,如 text/html、application/json |
Accept-Encoding | 内容协商 | 支持的压缩格式,如 gzip, deflate, br → 服务器可压缩响应体,节省带宽 |
Accept-Language | 内容协商 | 客户端语言偏好,如 zh-CN → 服务器返回中文内容 |
Cookie | 认证/状态 | 自动携带之前服务器设置的 Cookie,用于身份识别(如登录状态) |
Content-Type | 数据描述 | 请求体的数据类型(仅 POST/PUT 有),如 application/json |
Content-Length | 数据描述 | 请求体的字节数,帮助服务器读取完整数据 |
📌 提示:
- 浏览器会自动添加大部分请求头(如
Host、Connection、User-Agent) - 开发者可通过 JS(如
fetch)手动设置部分头(但受 CORS 限制) Accept-Encoding是性能优化关键,配合 Nginx 开启 gzip 可大幅减少传输体积
GET 和 POST 的区别?请求体在哪?
🎯 高频陷阱题!别只说“GET 有长度限制,POST 更安全”
| 对比项 | GET | POST |
|---|---|---|
| 用途 | 获取资源 | 提交数据 |
| 参数位置 | URL 查询参数(?key=value) | 请求体(Body)中 |
| 请求体 | ❌ 无(HTTP 规范不允许) | ✅ 有(如 JSON、form-data) |
| 缓存 | ✅ 可被浏览器缓存 | ❌ 一般不缓存 |
| 书签/历史 | ✅ 可收藏 | ❌ 不可直接收藏 |
| 幂等性 | ✅ 幂等(多次请求效果相同) | ❌ 非幂等(可能重复下单) |
| 安全性 | 参数暴露在 URL 中 | 相对更安全(但仍需 HTTPS) |
📌 重点澄清误区:
- “GET 有长度限制” → 实际是浏览器和服务器对 URL 长度的限制,不是协议规定
- “POST 更安全” → 错!不加密照样被截获,安全靠 HTTPS,不是方法
六、缓存机制详解:性能优化的核心
在发送请求前,浏览器会先检查缓存策略,以决定是否直接使用本地资源,避免重复请求。
缓存分为两大类:强缓存 和 协商缓存。
✅ 1. 强缓存(Strong Cache)
特点:不发起请求,直接使用本地缓存。
响应头字段:
| 字段 | 说明 |
|---|---|
Expires | HTTP/1.0,绝对时间(如 Expires: Wed, 21 Aug 2025 00:00:00 GMT) |
Cache-Control | HTTP/1.1,相对时间(如 max-age=3600) |
✅ 优先级:
Cache-Control>Expires
Cache-Control是 HTTP/1.1 的新标准,支持相对时间(如max-age=3600),不受客户端系统时间不准的影响;- 而
Expires是 HTTP/1.0 的旧标准,依赖绝对时间,如果用户修改了本地时间,缓存判断就会出错
示例代码(Node.js):
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
if (req.url === '/script.js') {
const js = fs.readFileSync('script.js', 'utf-8');
res.writeHead(200, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=3600, public' // 1小时强缓存
});
res.end(js);
}
}).listen(8888);
工作流程:
- 首次请求:服务器返回资源 +
Cache-Control: max-age=3600
- 再次请求:浏览器检查本地缓存未过期 → 直接使用(状态码
200 (from disk cache))
📌 优势:极大减少网络请求,提升加载速度。
✅ 2. 协商缓存(Conditional Cache)
当强缓存失效后,浏览器会发起请求,但通过验证机制判断资源是否更新。
响应头字段:
| 服务器返回 | 客户端请求 | 说明 |
|---|---|---|
ETag | If-None-Match | 资源内容的哈希值(如 md5(file)) |
Last-Modified | If-Modified-Since | 最后修改时间 |
✅ 优先级:
ETag>Last-Modified
Last-Modified精度只到秒级,如果文件在1秒内多次修改,无法感知变化。Last-Modified无法识别“内容未变但文件修改时间变了”的情况(误判为更新)。ETag是文件内容的哈希值(如 MD5),只要内容不变,ETag就不变,更精确。
示例代码(Node.js):
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
function md5(data) {
return crypto.createHash('md5').update(data).digest('hex');
}
http.createServer((req, res) => {
if (req.url === '/script.js') {
const filePath = path.join(__dirname, 'script.js');
const buffer = fs.readFileSync(filePath);
const fileMd5 = md5(buffer);
const noneMatch = req.headers['if-none-match'];
// 检查 ETag 是否匹配
if (noneMatch === fileMd5) {
res.statusCode = 304; // Not Modified
res.end();
return;
}
res.writeHead(200, {
'Content-Type': 'text/javascript',
'ETag': fileMd5,
'Cache-Control': 'max-age=0' // 强制走协商缓存
});
res.end(buffer);
}
}).listen(1234);
工作流程:
- 首次请求:
- 服务器返回资源 +
ETag: "abc123"
- 服务器返回资源 +
- 再次请求:
- 浏览器发送
If-None-Match: "abc123" - 服务器比对 ETag:
- 相同 → 返回
304 Not Modified(无响应体) - 不同 → 返回
200+ 新资源
- 相同 → 返回
- 浏览器发送
📌 优势:节省带宽,避免重复下载未变更资源。
七、服务器处理请求并返回响应
服务器接收到请求后:
- 解析路由(如
/search) - 执行后端逻辑(查询数据库、调用 API)
- 生成 HTML 内容
- 返回响应:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
ETag: "def456"
Cache-Control: max-age=600
<!DOCTYPE html>
<html>...</html>
八、浏览器接收响应并渲染页面
1. 解析 HTML,构建 DOM 树
浏览器逐行解析 HTML,构建文档对象模型(DOM Tree)。
2. 解析 CSS,构建 CSSOM 树
下载并解析 CSS 文件,构建CSS 对象模型(CSSOM Tree)。
⚠️ 阻塞渲染:CSS 是渲染阻塞资源。
3. 合并 DOM + CSSOM → Render Tree(渲染树)
只有可见节点 + 对应样式 → 构成渲染树。
4. 布局(Layout / Reflow)
计算每个节点在屏幕上的位置和大小。
5. 绘制(Paint / Rasterization)
将渲染树转换为像素,填充到屏幕。
6. 合成与显示(Composite)
分层绘制,GPU 加速合成最终画面。
九、执行 JavaScript
- 下载、解析、执行 JS 脚本
- JS 可能修改 DOM/CSSOM,触发重排(Reflow) 和 重绘(Repaint)
async、defer可优化脚本加载
十、总结:完整流程图解
输入 URL
↓
URL 解析与补全(https://baidu.com → https://www.baidu.com)
↓
DNS 解析(域名 → IP)
↓
TCP 三次握手 + TLS 握手(HTTPS)
↓
检查缓存:
├─ 强缓存命中? → 使用本地资源
└─ 否 → 发送 HTTP 请求
↓
服务器处理 → 返回响应
↓
浏览器解析 HTML/CSS/JS
↓
构建 DOM/CSSOM → Render Tree
↓
布局 → 绘制 → 合成 → 显示
结语
从输入 URL 到页面渲染,涉及网络、安全、缓存、渲染、JavaScript 执行等多个层面。掌握这一完整流程,不仅能提升前端开发效率,还能帮助你精准定位性能瓶颈。
尤其是缓存机制,作为前端性能优化的基石,必须深入理解其原理与应用场景。合理使用 Cache-Control、ETag 等策略,可显著提升用户体验与服务器负载。
掌握这些底层知识,你将真正成为一名懂原理的前端工程师。