文章

浏览器的历史、进程与渲染流水线

浏览器的历史、进程与渲染流水线

浏览器通常被理解为“打开网页的工具”,但这个说法掩盖了它真正承担的工作。一条 URL 输入地址栏后,浏览器需要判断输入意图、查询缓存、解析域名、建立网络连接、接收服务器响应、执行脚本、计算页面布局,并把最终结果交给 GPU 显示在屏幕上。一个网页之所以能够在几十到数百毫秒内出现,依赖的是一条跨越网络、操作系统和图形系统的协作链路。

这篇文章以 Chrome 的工作方式为主线,解释浏览器为什么从早期的单进程程序演变为多进程系统,以及一个 HTML 文档如何最终成为可交互的页面。目标不是记住所有内部名词,而是建立一张能够用来排查前端性能问题的心智地图。

对应视频:30分钟弄懂浏览器历史与渲染基本流程。本文是对该视频的学习整理。

浏览器首先解决的是“如何阅读万维网”

互联网和万维网并不是同一个概念。互联网提供的是底层网络:不同机器按照 IP、TCP 等协议交换数据。万维网(World Wide Web,WWW)则建立在这张网络之上,它约定了网页资源如何被标识、如何相互链接,以及浏览器应当如何获取和呈现这些资源。URL 用于定位资源,HTTP 用于传输资源,HTML 用于描述页面内容;三者共同构成了早期 Web 的基本表达方式。

早期浏览器的关键贡献,是让超文本不再只是带链接的文字。网页可以同时包含段落、图片、表单和脚本,用户也可以通过链接在资源之间跳转。随后,浏览器之间的竞争逐渐从“能否显示网页”转向“能否更快、更稳定地执行脚本和渲染复杂页面”。渲染引擎、JavaScript 引擎和 Web 标准由此成为浏览器架构的核心。

Chrome 与 Chromium 在这段演进中具有代表性。Chromium 是开源浏览器项目,Chrome 则在其基础上加入了 Google 的产品服务。它们采用 Blink 作为渲染引擎、V8 作为 JavaScript 引擎,并将多进程架构作为隔离和稳定性的基础。理解这些组件的分工,比追踪某个浏览器版本的市场历史更有助于理解今天的网页为何以这种方式运行。

单进程浏览器为什么难以可靠工作

浏览器必须同时处理网络、页面脚本、插件、用户输入和图形绘制。早期单进程浏览器把这些任务放在同一个进程中执行,这种设计实现简单,但它把稳定性、安全性和响应性绑定在了一起。

首先,单个页面或插件发生崩溃时,整个浏览器可能随之退出。其次,脚本执行、网络等待和绘制任务共享同一套资源;一个页面的长任务就可能使所有标签页失去响应。最后,网页脚本和插件若拥有过高权限,浏览器很难把不可信内容与本地系统资源隔离开来。

进程隔离为这些问题提供了更直接的边界。进程拥有相对独立的地址空间;一个进程中的异常通常不会直接破坏另一个进程的内存。线程则是进程中的执行单元,同一进程内的多个线程可以共享数据。浏览器把不同职责拆到不同进程后,便可以用操作系统提供的隔离能力控制风险,而不是只依赖页面代码自身的正确性。

Chrome 的多进程架构如何划分职责

Chrome 的多进程设计并不意味着“每个任务都新开一个进程”,而是为不同信任边界和资源类型安排相对独立的执行环境。一个典型页面会涉及浏览器进程、网络进程、渲染进程和 GPU 进程;历史上还存在插件进程等专门组件。它们通过进程间通信(IPC)传递消息和数据。

浏览器进程负责地址栏、标签页、窗口、权限和存储等用户可见的功能,也负责管理其他子进程。网络进程负责下载网络资源。渲染进程负责把 HTML、CSS 和 JavaScript 转化为页面结构与绘制命令;由于网页脚本是不可信代码,渲染进程通常处于沙箱中。GPU 进程则参与图层合成和图形加速。这样的分工使一个标签页卡顿或崩溃时,其他页面仍有机会保持可用。

多进程并非没有成本。进程之间不能直接共享普通内存,跨进程消息需要序列化和调度;每个渲染进程也会消耗额外内存。因此,浏览器还会根据站点隔离策略、标签页状态和系统资源决定进程如何复用。这里的重点不是“进程越多越好”,而是在安全、稳定和资源消耗之间取得可接受的平衡。

从输入 URL 到收到文档

用户在地址栏输入内容后,浏览器首先需要判断它是 URL 还是搜索关键词。若输入满足 URL 规则,浏览器会补全必要的协议并准备发起请求;若输入更像普通文本,则会交给默认搜索引擎。这一步看似微小,却说明地址栏并不是一个单纯的文本输入框,而是浏览器的导航控制台。

请求发出前,网络进程通常先检查缓存。若本地已有仍然有效的资源,浏览器可以直接使用它,避免网络往返;若缓存失效或不存在,才进入网络请求流程。随后,DNS 将域名解析为 IP 地址,TCP 建立可靠连接;若访问的是 HTTPS 页面,还需要在应用数据发送前建立 TLS 加密连接。HTTP 请求随后携带请求头和请求体到达服务器,服务器返回状态码、响应头和响应体。

响应头决定浏览器下一步如何处理数据。例如,重定向状态码会要求浏览器访问新的 URL;Content-Typetext/html 时,浏览器会将响应作为网页文档处理;application/octet-stream 等类型则可能进入下载流程。当浏览器确认收到 HTML 文档后,会创建或复用渲染进程,并把文档数据交给它。页面由旧内容变为白屏或开始刷新,正是导航控制权从旧文档切换到新文档的结果。

下面的流程图省略了许多实现细节,但给出了最重要的数据流向:

flowchart LR
    A["地址栏输入"] --> B["浏览器进程:判断 URL 或搜索"]
    B --> C["网络进程:缓存、DNS、TCP/TLS、HTTP"]
    C --> D["服务器响应"]
    D --> E["渲染进程:接收 HTML"]
    E --> F["解析、样式、布局、绘制"]
    F --> G["合成与 GPU"]
    G --> H["屏幕显示"]

HTML 为什么会变成 DOM 树

HTML 文件本身只是字符流,浏览器不能直接根据字符绘制页面。渲染进程中的 HTML 解析器会一边接收网络数据,一边把文本切分为 token,再依据标签的嵌套关系构建文档对象模型(Document Object Model,DOM)。DOM 是一棵树:html 节点包含 bodybody 又包含段落、图片、列表和其他子节点。

栈是构建 DOM 的关键工具。解析器读到开始标签时,将其压入栈并创建相应节点;读到结束标签时,弹出相匹配的开始标签。因为 HTML 可以流式解析,浏览器不必等待整个文档下载完成才开始工作。网络数据到达一部分,解析器就可以处理一部分,这也是页面能够逐步出现的原因之一。

脚本会改变这条流水线。普通 script 标签中的 JavaScript 有能力读取或修改 DOM,因此浏览器通常需要暂停后续 HTML 解析,先执行脚本。若脚本还需要从网络下载,暂停时间会更长。现代浏览器会利用预加载扫描等机制提前发现外部 CSS 与 JavaScript 资源,并尽早发起下载,但脚本的加载和执行方式仍会显著影响首屏速度。

CSS 如何参与页面结构的计算

DOM 只描述“页面有什么”,并不描述“页面看起来如何”。CSS 来自外部样式表、style 标签和元素的行内样式。浏览器解析 CSS 后,会生成可供后续计算使用的样式规则结构;随后结合选择器匹配、继承和层叠规则,为 DOM 中的元素计算最终样式。

这一步的结果并不是直接的像素,而是每个节点的确定属性:字体、颜色、宽高、边距、定位方式等。CSS 的层叠性意味着同一个属性可能有多个来源,浏览器必须根据来源、优先级和声明顺序决定谁生效。开发者工具中看到的“Computed”样式,正是这一步计算后的结果。

并非所有 DOM 节点都会被绘制。例如,display: none 的元素不会进入后续可见内容的结构。浏览器会综合 DOM 和已计算样式,构建用于布局的树,并计算每个可见节点在页面中的尺寸与位置。这个阶段通常称为 layout 或 reflow;它把抽象的结构和样式转化为具体的几何信息。

从布局到像素:绘制、分层与合成

布局完成后,浏览器已经知道每个元素“在哪里”,但尚未真正把它画到屏幕上。绘制阶段会为页面生成绘制记录,例如先画背景、再画文本、最后画边框。复杂页面并不会简单地从上到下画一遍;滚动、重叠、透明度、动画和 3D 变换都可能要求浏览器将内容划分为不同图层。

分层之后,合成线程会把图层切分为更小的图块。浏览器优先处理视口附近的图块,并通过栅格化把矢量形状、文本和绘制命令转换为位图。GPU 可以并行处理许多图形任务,因此常被用于加速栅格化和图层合成。合成器最终决定不同图层在屏幕上的叠放次序,并提交帧供显示系统呈现。

这条链路可以概括为:DOM 提供内容结构,样式计算提供视觉规则,布局提供几何位置,绘制提供图形命令,栅格化提供像素,合成提供最终画面。区分这些阶段,是理解浏览器性能问题的基础。

为什么重排、重绘和合成的代价不同

页面更新并不总是同样昂贵,关键取决于一次修改会让渲染流水线从哪一步重新开始。改变元素宽度、字体大小或文档结构,可能改变其他元素的位置,浏览器需要重新计算布局;这类操作通常被称为重排(reflow)。因为布局之后的绘制、栅格化和合成往往也要重新执行,重排的影响范围可能较大。

若只修改颜色、背景等不影响几何位置的样式,浏览器一般可以跳过布局,直接重新绘制相应区域;这类操作称为重绘(repaint)。它通常比重排轻,但仍会消耗绘制和栅格化资源。若修改可以由合成器独立处理的属性,例如某些 transformopacity 动画,浏览器有机会避免主线程上的布局和绘制,仅更新图层合成状态。这也是前端动画常建议优先使用变换和透明度的原因。

不过,“只用 transform 就一定更快”并不是普遍规律。图层过多会增加内存与合成开销,复杂滤镜和大面积动画同样可能造成性能问题。性能优化的正确起点是确认瓶颈位于脚本、布局、绘制、栅格化还是合成,而不是机械地套用某一种 CSS 写法。

用一张图理解浏览器,而不是背诵术语

浏览器的内部实现十分复杂,但初学阶段可以先把它理解为一个分层系统:网络层负责取回资源,渲染层负责理解文档,图形层负责生成像素,浏览器进程负责协调它们并保护系统边界。这个模型足以解释很多日常现象:为什么第一次打开网站比再次访问更慢,为什么一段长 JavaScript 会让页面卡住,为什么修改宽高比修改透明度更容易掉帧,以及为什么一个标签页崩溃不一定会让整个浏览器退出。

理解这条路径的真正价值,在于它能把“页面慢”转化为可以定位的问题。是 DNS 或服务器响应慢,还是缓存没有命中?是 JavaScript 阻塞了解析,还是 CSS 导致频繁重排?是图层过多,还是 GPU 栅格化出现瓶颈?当这些问题能够被放回同一条流水线中,浏览器性能就不再是一组零散的技巧,而成为可观察、可推理和可验证的工程问题。


术语速查

术语含义
URL资源在网络上的地址,例如一个网页链接。
DNS将域名解析为 IP 地址的系统。
DOM浏览器将 HTML 解析成的树形文档结构。
渲染进程解析网页、执行脚本并生成页面绘制结果的受限进程。
Layout / Reflow计算可见元素尺寸与位置的阶段。
Paint / Repaint生成页面绘制命令,或因样式变化重新绘制的过程。
Rasterization将绘制命令转换为像素位图的过程。
Compositing将多个图层组合为最终画面的过程。
本文由作者按照 CC BY 4.0 进行授权