浏览器工作原理
Category(分类): Browser, Network, Rendering Status: 已更新
女朋友:浏览器原理为什么要学这么多?
我:因为网络请求、页面渲染、JavaScript 执行、进程线程和 Web 安全并不是互相独立的知识。把它们放到浏览器架构里,可以看到一条完整链路:
输入 URL
-> 导航与网络
-> 创建/选择渲染上下文
-> HTML/CSS/JavaScript 解析
-> 样式、布局、绘制、合成
-> 用户看到并交互的页面
原文以 Chrome/Chromium 为主要例子,并混合了浏览器进程、HTTP 请求、导航流程和渲染流水线。Chrome、Edge 等 Chromium 浏览器具有代表性,但浏览器内部进程、线程和调度属于实现细节,不应该把某个版本的架构图当成所有浏览器的永久规则。
一、进程、线程和并行处理
1. 进程是什么
进程是程序运行时的资源和隔离边界,通常拥有自己的地址空间、代码、数据和至少一个线程。操作系统可以调度多个进程并行或交错执行。
2. 线程是什么
线程是进程中的执行单元,同一进程的线程通常共享部分内存和资源,因此通信快,但也需要处理并发访问、竞态和同步问题。线程不能脱离进程单独作为普通应用资源存在。
原文用下面的任务说明并行:
A = 1 + 2
B = 20 / 5
C = 7 * 8
显示 A、B、C
如果 A、B、C 彼此独立,多个线程或 Worker 可能并行计算;但创建线程、传递数据、同步和调度也有成本,并不是线程越多越快。
3. 浏览器为什么需要隔离
浏览器同时运行:
- 浏览器界面和标签页管理;
- 多个网站的 JavaScript 和 DOM;
- 网络、缓存、存储和下载;
- 图片、视频、字体和 GPU 绘制;
- 扩展、插件和开发者工具。
如果所有功能在同一个进程中,一个页面的无限循环、渲染崩溃或插件错误可能影响整个浏览器。多进程带来稳定性、性能隔离和安全沙箱,但也增加内存和进程间通信成本。
二、从单进程到多进程浏览器
1. 单进程时代
早期浏览器可能把网络、渲染、脚本、插件和 UI 放在一个进程中。

优点是模块之间通信简单,缺点是:
- 一个模块崩溃可能拖垮整个浏览器;
- 一个页面的长脚本可能阻塞其他页面和浏览器 UI;
- 所有网站共享更多高权限资源,安全边界更弱;
- 资源泄漏不容易通过关闭单个页面回收。
2. 多进程时代
现代 Chromium 常见的高层组件包括:
- Browser process:地址栏、标签页、窗口、浏览器 UI、子进程管理和导航协调;
- Renderer process:Blink、V8、DOM、样式、布局和页面脚本;通常按站点/标签页等策略分配;
- Viz/GPU 相关进程:聚合多个渲染上下文并完成栅格化、绘制或 GPU 协作;
- Network/服务进程:网络请求、缓存、代理、DNS 等,具体是否是独立进程随版本和平台变化;
- Utility process:音视频解码、数据处理等隔离服务;
- Extension/插件相关进程:扩展和兼容插件的隔离运行环境。


进程间通过 IPC 或浏览器内部消息机制协作。一个页面进程崩溃时,浏览器可以显示“页面崩溃”,而不必让全部浏览器退出;但浏览器主进程、GPU/Viz、操作系统内存压力和共享服务出现问题时,影响范围仍可能扩大。
3. 安全沙箱
渲染进程通常在沙箱中运行,限制网页代码直接访问操作系统敏感资源。沙箱不是“网页绝对无法攻击系统”的保证:
- 浏览器和操作系统仍需要持续修复漏洞;
- 权限更高的浏览器进程和扩展会影响安全边界;
- 网站自身的 XSS、供应链和错误消息权限仍然危险;
- 沙箱限制的是进程能力,不是对 DOM、Cookie 或同源数据的业务授权。
因此要组合使用进程隔离、沙箱、同源策略、Site Isolation、CSP、权限策略和安全更新。
4. Site Isolation 和进程分配
“一个标签页一个渲染进程”是过于绝对的旧说法。现代浏览器会综合站点隔离、是否由另一个页面打开、iframe 的站点、设备内存和浏览器策略决定:
- 不同站点的跨站 iframe 通常隔离到不同渲染进程,以保护站点之间的性能和安全;
- 同一站点的页面可能因为 opener 关系或内存策略共享进程,也可能各自使用进程;
- 同源、同站点、同一进程是三个不同概念;
- 进程分配可随浏览器版本、平台、内存压力和 WebView 宿主变化。
原文提到的 process-per-site-instance 是 Chromium 历史/实现策略名,不能作为开发者用来预测所有页面进程的 API。
三、当前架构是固定的吗?
不是。Chromium RenderingNG 文档描述了渲染任务如何拆分到主线程、合成线程、Viz 和辅助线程;不同平台可能采用 CPU 回退、不同 GPU 路径或不同进程结构。
“面向服务的架构(Services Oriented Architecture)”可以保留作为浏览器架构演化方向:把 UI、网络、存储、文件、设备等能力拆成可复用的服务,按平台和资源情况组合。但它是内部架构方向,不是网页代码可以直接依赖的固定进程清单。
四、从输入 URL 到导航提交

下面以输入 https://example.com/ 为例。实际浏览器会并行预连接、读取缓存、处理扩展和安全策略,以下是便于学习的主路径。
1. 地址栏处理
浏览器先判断输入内容是 URL 还是搜索关键词:
- URL 缺少协议时,浏览器可能补全或使用默认搜索引擎;
- 完整 URL 包含 scheme、host、port、path、query 和 fragment;
- 用户回车后,地址栏显示加载状态,但旧页面可能继续显示,直到新导航提交。
beforeunload 可以在存在未保存内容时提示用户,但不应滥用。现代浏览器限制自定义提示文本,也可能不在所有场景触发;更可靠的是自动保存草稿和使用 visibilitychange 处理页面离开。
2. 发起导航
浏览器进程协调导航,网络服务根据 URL 和策略发起请求。是否复用当前渲染进程、是否创建新进程、是否需要下载管理器,会在导航过程中决定。
3. 查找缓存
浏览器可能命中 HTTP 缓存、Service Worker、预加载资源或 BFCache。命中 BFCache 时可能直接恢复历史页面快照,并非重新加载 HTML。
缓存未命中或需要验证时,才进入 DNS、连接、TLS 和 HTTP 请求流程。缓存的详细规则见前端缓存。
4. DNS、连接和安全协议
浏览器可能先查找浏览器/操作系统/本地解析器缓存,再通过 DNS 获取地址。实际可能使用 DNS over HTTPS、DNS over TLS、代理、本地 hosts 或预解析。
连接协议取决于服务器和客户端协商:
- HTTP/1.1 通常通过 TCP;
- HTTP/2 通过 TLS 上的 TCP,并在一条连接上复用多个流;
- HTTP/3 使用 QUIC/UDP;
- HTTPS 还要完成 TLS 握手和证书校验。
不要再把“同一个域名最多 6 个 TCP 连接”作为现代浏览器的固定规则。连接数和并发流受 HTTP 版本、代理、网络地址、连接复用、优先级、浏览器实现和服务器配置影响。
默认端口通常是:
http -> 80
https -> 443
如果 URL 显式写了端口,则使用显式端口。
5. HTTP 请求
HTTP/1.1 请求大致包括:
GET / HTTP/1.1
Host: example.com
Accept: text/html
Accept-Encoding: br, gzip
Connection: keep-alive
浏览器还可能附加 Cookie、缓存验证器、语言、客户端提示和安全相关请求头。HTTPS 中的 HTTP 内容会在 TLS 连接内传输,网络中间人不能直接读取明文请求体。
HTTP/2/3 不再以完全相同的文本格式在网络上传输,但请求语义仍包含方法、目标、头和可选请求体。
6. 重定向
服务器可能返回:
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/
浏览器读取 Location 后继续导航。重定向会增加延迟,还可能改变方法、缓存和安全上下文。服务端应减少不必要的链式重定向,并校验开放重定向参数。
301、308 通常表达永久重定向,302、303、307 有不同的方法保留语义,不应统称为“301 都一样”。
7. 服务端响应
响应包括状态行、响应头和响应体:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache
Content-Length: 1234
<!doctype html>
Content-Type 会影响浏览器处理方式:
text/html通常交给文档导航和渲染流程;application/javascript等脚本资源交给脚本加载器;application/octet-stream常被当作下载内容,但最终仍受Content-Disposition、浏览器策略和文件类型影响;- MIME 配置错误可能造成页面下载、脚本拒绝执行或安全问题。
8. Commit Navigation
网络服务得到响应头后,浏览器进程根据安全策略、响应类型、站点隔离和渲染进程状态,向目标渲染进程发送导航提交信息。渲染进程与网络服务建立数据管道,接收 HTML 字节流。
渲染进程确认能够接收并处理文档后,浏览器更新地址栏、历史记录、安全状态和加载状态。此时页面可能先是空白或部分内容,之后才进行 HTML 解析和子资源加载。
五、HTML 解析和资源加载
1. 字节到 DOM
HTML 的大致处理过程:
字节 -> 字符解码 -> 令牌化 -> 节点 -> DOM
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/app.css">
<script type="module" src="/app.js"></script>
</head>
<body>
<main><h1>Hello</h1></main>
</body>
</html>
HTML 解析器不只是简单地按字符串分割标签,还要处理错误恢复、脚本插入、表格规则和自定义元素等 HTML 标准行为。
2. 预加载扫描器
浏览器通常会在主 HTML 解析之外扫描即将加载的资源,提前发现 CSS、脚本、图片和字体。这是实现细节,但能解释为什么某些资源在 DOM 尚未构建完成时已经开始请求。
3. 脚本对解析的影响
经典脚本默认会暂停 HTML 解析并执行:
<script src="/app.js"></script>
更现代的选择:
<script src="/analytics.js" async></script>
<script src="/app.js" defer></script>
<script type="module" src="/entry.js"></script>
async下载不阻塞解析,下载完成后尽快执行,多个脚本顺序不保证;defer在 HTML 解析完成后执行,经典脚本之间保持顺序;- 模块脚本默认延迟执行,并按依赖图加载;
- 执行时间过长仍会阻塞主线程、交互和渲染。
4. CSS 对脚本和渲染的影响
样式表通常是渲染阻塞资源,浏览器需要样式信息才能安全绘制。CSS 不一定让 HTML 解析在所有情况下完全停止,但位于样式表之后的经典脚本可能等待样式表,因为脚本可能读取计算样式:
<link rel="stylesheet" href="/app.css">
<script>
console.log(getComputedStyle(document.body).color)
</script>
不要用 setTimeout 推断 CSS 是否阻塞解析;要结合脚本位置、脚本类型、样式依赖和 DevTools 实测。
六、渲染流水线
现代 Chromium 的内部流水线比“DOM + CSSOM -> Render Tree -> Layout -> Paint -> GPU”更细。可以用下面的学习模型:
- Style:根据 CSS 规则生成计算样式;
- Layout:计算元素尺寸和位置,生成布局片段/布局树;
- Pre-paint:准备属性树、裁剪和绘制失效;
- Paint:生成描述文本、背景、边框和阴影的显示列表;
- Layerize:根据需要组织合成层;
- Raster:把绘制列表、图片和字体等栅格化为纹理/位图;
- Activate/Composite:合成视觉效果、滚动和各层;
- Aggregate/Draw:由 Viz/GPU 等组件聚合并绘制到屏幕。

实际阶段可以跳过:例如一个满足条件的 transform 动画可能不需要重新布局和绘制;但“所有 transform 都只走 GPU”是不准确的。
1. DOM 和计算样式
DOM 是文档结构;CSSOM 是浏览器提供的 CSS 对象和规则模型,document.styleSheets 只是访问其中一部分的接口。计算样式可以通过:
const style = getComputedStyle(document.querySelector('.card'))
console.log(style.display, style.width, style.color)
计算样式受继承、层叠、选择器匹配、媒体查询、变量、字体和用户代理样式影响。red、2em 等值会在计算/使用过程中解析为适合布局和绘制的值,但不要把所有内部中间结构都简化成一个公开的“CSSOM 树”。
2. 布局树
布局阶段需要知道可见盒子的几何信息:
display: none的节点通常不参与布局;head、meta等不产生可见布局盒;visibility: hidden通常仍占据布局空间;- 伪元素、匿名盒、列表标记和 iframe 可能产生额外布局对象;
- 一个节点的尺寸变化可能影响父节点、兄弟节点和后代节点。
DOM + computed style -> layout boxes/fragments -> size/position
“渲染树”是历史教学术语;现代引擎内部可能使用 Layout Tree、fragment tree 和 property trees 等不同结构。

3. 绘制和栅格化
绘制阶段不是直接把每个 DOM 节点变成屏幕像素,而是生成按顺序排列的绘制指令/显示列表,例如:
绘制背景 -> 边框 -> 文本 -> 阴影 -> 图片

之后浏览器把绘制列表划分为适合处理的区域或 tile,再进行栅格化。

栅格化可能使用 GPU,也可能因平台、驱动、内容和资源情况回退到 CPU。GPU 是实现优化的一部分,不是网页开发者可以强制打开的万能开关。
七、图层与合成

浏览器不会为每个 DOM 节点创建独立图层。图层化由合成需求和浏览器启发式算法决定,可能受以下因素影响:
- transform、opacity、filter 等视觉效果;
- 视频、canvas、滚动容器和固定定位;
- 3D 变换和动画;
- 裁剪、遮罩、混合和层叠关系;
will-change提示;- 浏览器平台和内存预算。
层叠上下文不等于合成层。position 加 z-index、opacity、transform 等可以创建层叠上下文,但不代表一定被提升为独立合成层。
.animated {
will-change: transform;
}
.animated.is-idle {
will-change: auto;
}
will-change 是提前提示,长期对大量元素使用会增加内存和栅格化成本。原文建议到处使用 translateZ(0) 强制 GPU,这种做法不应作为现代通用方案。
八、回流、重绘和合成
1. 布局更新(Reflow/Layout)
修改会影响几何尺寸、位置或文档流的内容,可能触发布局:
- 宽高、内外边距、边框、字体和文本内容;
- 添加、删除和移动节点;
- 改变容器尺寸或布局模式。
布局范围取决于布局模型和失效范围,不是每次都重算整棵 DOM 树。
2. 重绘(Paint)
颜色、背景、阴影等不影响几何的变化可能只需要重新绘制,但仍可能有较高成本,尤其是大面积阴影、滤镜和复杂文本。
3. 合成(Composite)
如果属性满足合成条件,浏览器可能只更新合成属性,例如某些 transform 和 opacity 动画:
.panel {
transition: transform 200ms ease, opacity 200ms ease;
}
这通常比反复修改 top、left 更容易避免布局,但不是保证。大型图层、透明重叠、滤镜和栅格化仍可能造成内存或 GPU 压力。
4. 强制同步布局
读取几何属性不会无条件触发布局;当之前有尚未处理的 DOM/CSS 写入,而读取必须得到最新布局时,浏览器可能强制同步布局:
// 容易形成读写交替
for (const item of items) {
item.style.width = `${container.offsetWidth}px`
}
// 先集中读取,再集中写入
const width = container.offsetWidth
for (const item of items) {
item.style.width = `${width}px`
}
常见可能触发布局的读取包括 offsetWidth、clientHeight、scrollTop、getBoundingClientRect() 和部分 getComputedStyle() 使用。是否真的强制布局取决于样式是否失效、读取属性和浏览器实现。
5. 现代优化
- 使用
requestAnimationFrame安排视觉写入; - 批量读写,避免读写交替;
- 对滚动、resize、输入和 pointermove 做有意义的节流;
- 动画优先考虑 transform/opacity,但用 Performance 面板验证;
- 对大量列表使用虚拟化、分页或
content-visibility: auto; - 图片预留尺寸,减少 CLS;
- 把复杂计算放进 Worker,不要把 Worker 当成 DOM 并行修改工具。
九、事件循环和一帧
页面 JavaScript 与许多 DOM、样式和布局任务共享渲染主线程。一个简化的帧处理过程可能是:
执行 task
-> 清空 microtasks
-> 运行合适的 requestAnimationFrame 回调
-> 样式/布局/绘制/合成
-> 下一次渲染机会
但浏览器可以跳过渲染、合并任务、节流后台页面,也可以让滚动和部分动画在合成线程继续运行。不要把 60Hz、16.67ms 和“每轮必渲染”当成绝对规则。
requestAnimationFrame(() => {
element.style.transform = `translateX(${nextX}px)`
})
如果一个同步任务超过几十毫秒,即使后续 CSS 能合成,主线程也可能无法及时处理输入和提交更新。性能问题要结合 Long Task、INP、帧图和渲染流水线判断。
十、页面生命周期和资源回收
关闭标签页后,浏览器可能回收对应渲染进程和资源,但不能把它当成解决内存泄漏的唯一方法:
- SPA 在不离开页面时可能长期积累监听器、定时器和缓存引用;
- Worker、WebSocket、Observer 和 IndexedDB 事务需要主动清理;
- 浏览器可能冻结、回收或恢复后台页面;
- BFCache 会保存页面快照,页面不一定立即销毁。
组件卸载时清理:
const controller = new AbortController()
window.addEventListener('resize', onResize, {
signal: controller.signal
})
const worker = new Worker('/worker.js')
function dispose() {
controller.abort()
worker.terminate()
}
十一、把原文的 HTTP 八阶段更新为现代链路
原文将 HTTP 请求概括成:构建请求、查找缓存、准备 IP/端口、等待 TCP 队列、建立 TCP、发送请求、服务器处理、返回并断开。这个学习框架仍然有价值,但现代链路应补充:
- URL 解析和导航策略;
- HTTP 缓存、Service Worker 和 BFCache 判断;
- DNS、代理、连接复用和地址选择;
- HTTP/1.1、HTTP/2 或 HTTP/3 协议协商;
- HTTPS 的 TLS 和证书校验;
- 请求优先级、流复用和服务器处理;
- 重定向、响应头、压缩和缓存更新;
- 导航提交、HTML 流式解析和子资源并行加载;
- 渲染进程解析、样式、布局、绘制和合成。
“TCP 四次挥手后一定断开”也不是每个现代请求的固定结局:Keep-Alive、HTTP/2 多路复用和 HTTP/3 连接都可以复用,服务器和浏览器会依据协议与策略决定连接生命周期。
十二、面试题和正确回答
1. 为什么第二次访问页面可能更快?
可能原因包括:
- HTML、JS、CSS、图片命中 HTTP 缓存;
- DNS、连接、TLS 或预连接状态复用;
- Service Worker 直接提供缓存;
- 页面从 BFCache 恢复;
- CDN 命中、服务器热缓存和数据恢复。
不能只回答“因为 Memory Cache”,也不能把后退恢复和普通重新导航混为一谈。
2. 打开 Chrome 一个 Tab 至少几个进程?
没有可靠的固定答案。浏览器可能已有 Browser、Viz/GPU、网络/服务进程,一个新页面还可能使用或创建渲染进程,扩展、视频解码和 WebView 又会改变数量。正确回答应是“由版本、平台、站点隔离、标签页关系、内存压力和启用功能决定”,而不是固定“四个”。
3. 为什么一个页面卡死有时会影响其他页面?
常见原因:
- 页面与其他标签页共享渲染进程或相同站点资源;
- Browser、Viz/GPU 或其他共享服务进程故障;
- 系统内存压力导致整体换页或回收;
- 扩展、驱动或浏览器自身崩溃;
- 进程间通信或 GPU 资源出现问题。
但一个页面的普通无限循环通常主要阻塞自己的渲染主线程,不应说多进程“完全隔离一切影响”。
4. 同源和同站点有什么区别?
- 同源:协议、主机、端口都相同,决定许多 DOM、Storage 和脚本访问边界;
- 同站点(schemeful site):通常基于 scheme 和 registrable domain,用于 Cookie、站点隔离等更高层策略;
https://a.example.com和https://b.example.com可能同站点但不同源;https://example.com:443和https://example.com:8443不是同源,但站点层面的规则不能直接替代源检查。
不要把“同顶级域和二级域”直接等同于同源。
十三、性能工具如何验证
浏览器原理不是背图,应该用工具观察:
Network
- DNS、连接、TLS、TTFB、请求优先级;
- HTTP 状态、缓存命中、
Age、ETag、压缩和Server-Timing; - HTML、CSS、字体和 LCP 图片的关键路径;
- HTTP/2/3、协议、连接复用和请求瀑布。
Performance
- 主线程任务、Long Task、输入延迟;
- Style、Layout、Paint、Composite 时间;
- 帧时间、掉帧、动画和强制同步布局;
- JS 调用栈和第三方脚本。
Rendering/Layers
- 图层数量和内存;
- 合成原因;
- Paint flashing、Layout Shift Regions;
- GPU 栅格化和回退情况。
工具展示的是当前浏览器的实现证据,不应据此推导所有浏览器内部都完全相同。
十四、原文中需要保留但必须加限定的内容
- 进程隔离提高稳定性和安全性,但不是所有 Tab 一进程;
- 渲染主线程通常执行 JS、DOM、样式和布局,但合成、栅格化和 GPU 也会参与;
- CSS/DOM/JavaScript 共同决定渲染结果,经典脚本可能阻塞解析;
- DOM、计算样式、布局、绘制和合成是理解性能的有效模型;
transform/opacity常常适合动画,但不保证一定绕过 Layout/Paint;- Memory/Disk Cache、连接数、进程数量和线程名称属于实现细节;
- 同站点页面可能共享某些资源或进程,但不能因此跳过同源和权限检查;
- GPU 可加速部分工作,也可能因为驱动、平台和内容回退到 CPU。
总结
- 浏览器是由多个进程、线程和服务协作的复杂宿主,具体结构随版本和平台变化;
- 多进程、沙箱和 Site Isolation 提供稳定性和安全隔离,但不是绝对隔离;
- 从 URL 到页面显示要经过缓存、DNS、连接、TLS、HTTP、导航提交、HTML 解析和渲染流水线;
- 不要把“每域 6 个连接”“每 Tab 一个进程”“GPU 直接绘制”“每任务后必渲染”等历史结论当成硬规则;
- 渲染可按 Style、Layout、Pre-paint、Paint、Layerize、Raster、Composite、Draw 理解;
- CSSOM、DOM、布局树和合成层是相关但不同的概念;
- 主线程长任务会影响输入和渲染,Worker、分块、rAF 和批量读写可以改善体验;
- 通过 Network、Performance、Rendering 和 Layers 面板验证真实瓶颈。
参考资料
- Chrome:RenderingNG architecture
- Chrome:RenderingNG overview
- MDN:Browser architecture
- MDN:JavaScript execution model
- MDN:Critical rendering path
- web.dev:Rendering performance
- web.dev:How browsers work
- MDN:Navigation and resource timing
- MDN:Same-origin policy
- MDN:Content Security Policy
- HTML Standard:Processing model