从输入 URL 到页面渲染:深入浏览器工作全过程

175 阅读13分钟

从输入 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)

ed9333cac70d6b279812bd971f31940e.png

关键点:每个标签页通常运行在一个独立的渲染进程中,实现进程隔离,防止一个页面崩溃影响整个浏览器。


二、输入 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 查询流程:

  1. 检查本地 Hosts 文件
  2. 查询本地 DNS 缓存(浏览器、操作系统)
  3. 向配置的 DNS 服务器发起请求(递归查询)
  4. DNS 服务器进行迭代查询(根 → 顶级域 → 权威 DNS)
  5. 返回 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

💡 目的:确保客户端和服务器之间建立一条可靠的双向通信通道

整个过程就像两个人打电话确认是否能正常通话:

  1. 第一次握手(SYN)
    客户端发送一个 SYN=1 的报文,并携带一个随机生成的序列号 seq=x,表示:“我想和你建立连接”。
    → 此时客户端进入 SYN_SENT 状态。
  2. 第二次握手(SYN+ACK)
    服务器收到后,回应一个 SYN=1, ACK=1 的报文,确认客户端的 SYN,并带上自己的初始序列号 seq=y,同时对客户端的 x 进行确认 ack=x+1
    → 表示:“我收到了你的请求,我也准备好连接了。”
    → 此时服务器进入 SYN_RECEIVED 状态。
  3. 第三次握手(ACK)
    客户端收到后,再发送一个 ACK=1 的报文,确认服务器的 y,即 ack=y+1
    → 表示:“我也收到你的回复了,连接可以开始了。”
    → 双方进入 ESTABLISHED 状态,连接建立成功。

为什么是三次?不能两次吗?

因为 TCP 是全双工通信,需要双方都确认“我能发,也能收”。
如果只有两次,服务器无法确认客户端是否收到了自己的响应(比如 SYN 丢了),可能导致“半开连接”或资源浪费。

2. TLS/SSL 加密握手(HTTPS)

为了安全传输,还需进行 TLS 握手:

  • 客户端发送支持的加密套件
  • 服务器返回证书(含公钥)
  • 验证证书有效性(CA 签发、有效期、域名匹配)
  • 双方协商生成“会话密钥”
  • 后续通信使用对称加密

🔐 安全提示:浏览器会检查证书是否可信,否则提示“不安全”。 💡 目的:在已建立的 TCP 连接上,协商加密算法、验证身份、生成会话密钥,实现数据加密、防篡改、身份认证

以最常见的 TLS 1.2 为例:

  1. Client Hello
    客户端发送支持的 TLS 版本、加密套件列表(如 RSA、ECDHE)、随机数 Client Random

  2. Server Hello
    服务器选择一个双方都支持的加密套件,并返回:

    • 选定的 TLS 版本和加密套件
    • 服务器随机数 Server Random
    • 数字证书(包含公钥、域名、CA 签发信息)
  3. 证书验证
    客户端验证证书:

    • 是否由可信 CA 签发
    • 域名是否匹配
    • 是否在有效期内
      ❌ 如果验证失败,浏览器会弹出“不安全”警告。
  4. 密钥交换(以 RSA 为例)

    • 客户端生成一个预主密钥(Pre-Master Secret)
    • 用服务器证书中的公钥加密后发送给服务器
  5. 生成会话密钥

    • 双方使用 Client Random + Server Random + Pre-Master Secret 通过算法生成相同的会话密钥(Session Key)
    • 后续通信使用这个对称加密密钥(因为对称加密效率高)
  6. Finished 消息

    • 双方发送加密的 Finished 消息,验证握手是否成功
    • 握手完成,开始使用对称加密传输数据

为什么用“混合加密”?

  • 非对称加密(如 RSA)用于安全地传输“会话密钥”,解决密钥分发问题
  • 对称加密(如 AES)用于加密实际数据,性能更高

📌 总结:一张表看懂所有术语

名称所属协议类型作用
SYNTCP标志位发起连接请求,同步序列号
ACKTCP标志位确认收到数据或连接请求
Client RandomTLS随机数参与生成会话密钥,增加随机性
Server RandomTLS随机数同上
CertificateTLS数字证书验证服务器身份,提供公钥
Pre-Master SecretTLS秘密随机数客户端生成,用公钥加密传输,用于生成会话密钥

💡 小贴士:如何记住?

你可以想象这是一个“安全建联”的过程:

  1. 打电话接通 → TCP 三次握手(SYN, ACK)
  2. 确认对方身份 → 检查 Certificate
  3. 商量一个只有你们知道的密码 → 用 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(获取资源),其他还有 POSTPUTDELETE 等
  • 请求路径/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/htmlapplication/json
Accept-Encoding内容协商支持的压缩格式,如 gzip, deflate, br → 服务器可压缩响应体,节省带宽
Accept-Language内容协商客户端语言偏好,如 zh-CN → 服务器返回中文内容
Cookie认证/状态自动携带之前服务器设置的 Cookie,用于身份识别(如登录状态)
Content-Type数据描述请求体的数据类型(仅 POST/PUT 有),如 application/json
Content-Length数据描述请求体的字节数,帮助服务器读取完整数据

📌 提示

  • 浏览器会自动添加大部分请求头(如 HostConnectionUser-Agent
  • 开发者可通过 JS(如 fetch)手动设置部分头(但受 CORS 限制)
  • Accept-Encoding 是性能优化关键,配合 Nginx 开启 gzip 可大幅减少传输体积

GET 和 POST 的区别?请求体在哪?

🎯 高频陷阱题!别只说“GET 有长度限制,POST 更安全”

对比项GETPOST
用途获取资源提交数据
参数位置URL 查询参数(?key=value请求体(Body)中
请求体❌ 无(HTTP 规范不允许)✅ 有(如 JSON、form-data)
缓存✅ 可被浏览器缓存❌ 一般不缓存
书签/历史✅ 可收藏❌ 不可直接收藏
幂等性✅ 幂等(多次请求效果相同)❌ 非幂等(可能重复下单)
安全性参数暴露在 URL 中相对更安全(但仍需 HTTPS)

📌 重点澄清误区

  • “GET 有长度限制” → 实际是浏览器和服务器对 URL 长度的限制,不是协议规定
  • “POST 更安全” → 错!不加密照样被截获,安全靠 HTTPS,不是方法

六、缓存机制详解:性能优化的核心

在发送请求前,浏览器会先检查缓存策略,以决定是否直接使用本地资源,避免重复请求。

缓存分为两大类:强缓存协商缓存


✅ 1. 强缓存(Strong Cache)

特点:不发起请求,直接使用本地缓存。

响应头字段:
字段说明
ExpiresHTTP/1.0,绝对时间(如 Expires: Wed, 21 Aug 2025 00:00:00 GMT
Cache-ControlHTTP/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);
工作流程:
  1. 首次请求:服务器返回资源 + Cache-Control: max-age=3600

image.png

  1. 再次请求:浏览器检查本地缓存未过期 → 直接使用(状态码 200 (from disk cache)

9a738482da88dafa046d373e1a3cb861.png

📌 优势:极大减少网络请求,提升加载速度。


✅ 2. 协商缓存(Conditional Cache)

当强缓存失效后,浏览器会发起请求,但通过验证机制判断资源是否更新。

响应头字段:
服务器返回客户端请求说明
ETagIf-None-Match资源内容的哈希值(如 md5(file)
Last-ModifiedIf-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);
工作流程:
  1. 首次请求:
    • 服务器返回资源 + ETag: "abc123"

image.png

  1. 再次请求:
    • 浏览器发送 If-None-Match: "abc123"
    • 服务器比对 ETag:
      • 相同 → 返回 304 Not Modified(无响应体)
      • 不同 → 返回 200 + 新资源

image.png

📌 优势:节省带宽,避免重复下载未变更资源。


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

服务器接收到请求后:

  1. 解析路由(如 /search
  2. 执行后端逻辑(查询数据库、调用 API)
  3. 生成 HTML 内容
  4. 返回响应:
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)
  • asyncdefer 可优化脚本加载

十、总结:完整流程图解

输入 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-ControlETag 等策略,可显著提升用户体验与服务器负载。

掌握这些底层知识,你将真正成为一名懂原理的前端工程师