浏览器的渲染过程是多个线程、进程和阶段的复杂编排,它将原始的 HTML、CSS 和 JavaScript 转换为屏幕上的交互像素。
-
你在浏览器中输入一个 URL 并按下回车键
-
网站在你的屏幕上呈现出来
注意:本文中,将使用 “客户端(client)”、“浏览器(browser)” 和 “服务器(server)” 术语。在这里,“浏览器” 是一种 “客户端”,在本文的语境中,它们是一个东西,而 “服务器”是指托管网站的网络服务器。
导航与网络
当你在浏览器的地址栏中输入一个 URL 并按下回车键时,渲染过程就开始了。假设我们正在尝试加载 “https://example.com”。浏览器不知道从哪里获取这个网站的内容。这只是一个我们人类能够理解的名称,浏览器理解不了——它需要将这个名称转换为一个 IP 地址——这就是 DNS 查找发挥作用的地方了。
提示:DNS 代表域名系统(Domain Name System)。这个系统使用指定的权威域名服务器将域名映射到数字 IP 地址。
DNS 查询步骤 Demo 演示:
上图展示了 DNS 的查找过程,也称为查询之旅(query journey)。查询之旅涉及将你输入的域名转换为 IP 地址所需的每一个步骤。查询的第一站是递归服务器(recursive server),然后它会联系一系列权威域名服务器将域名转换为 IP 地址。最终,返回请求域名所对应的 IP 地址。
提示:域名服务器是用于查找 IP 地址所关联域名的专用服务器。你可以在域名的 DNS 记录(domain’s DNS records)中找到这些域名服务器。
现在我们有了 IP 地址,浏览器就可以与托管网站的服务器建立连接了。建立连接是通过 TCP 握手过程完成的。
TCP/TLS 握手
TCP(传输控制协议)握手是一个包含三个步骤的过程,用于在客户端和服务器之间建立连接。它由客户端和服务器之间交换的一组数据包组成,以确保连接可靠。这三个步骤分别是:
-
SYN:客户端向服务器发送一个同步数据包(SYN)来发起连接
-
SYN-ACK:服务器用一个确认数据包(SYN-ACK)响应客户端
-
ACK:客户端向服务器发送一个确认(ACK)来完成握手
到这一步,我们只是与服务器建立了连接。下一步是确保这个连接的安全性。还记得 URL 旁边的 “https” 锁图标吗?这定义了一个安全连接,是通过另一个称为 TLS 握手的握手过程完成的,它涉及几个步骤。但在我们深入探讨之前,如果你能理解一些与 TLS 握手相关的术语,会更容易掌握。
-
密码(Cipher):一种用于加密和解密数据的算法,用于在互联网上安全地传输数据
-
证书(Certificate):一个文件,包含有关网站身份和公钥(public key)的信息,用作数字签名来验证网站的真实性
-
证书颁发机构(Certificate Authority,CA):一个受信任的实体,向网站颁发数字证书,由它来验证网站的身份并确保证书的真实性
-
公钥(Public Key):用于加密发送至互联网的数据,在服务端与客户端中共享,确保建立安全连接
-
私钥(Private Key):用于解密从客户端收到的数据,它是保密的,不对外共享
TLS 握手过程包含多个步骤,我将它主要概括为 4 个类别:
-
问候消息(Hello Messages):客户端发送一个 “ClientHello” 消息来发起握手,服务器用一个 “ServerHello” 消息加上数字证书进行响应
-
密钥交换(Key Exchange):服务器可发送密钥交换信息,客户端则回应自己的密钥交换信息(如有要求,还可提供证书)
-
密码规范(Cipher Spec):客户端发送一个 “CipherSpec” 消息以切换到协商好的加密方式,然后发送一个 “Finished” 消息
-
服务器完成(Server Finalization):服务器回复自己的 “CipherSpec” 和 “Finished” 消息,完成握手并建立安全连接
提示:SSL,即安全套接层(Secure Sockets Layer),是最初为 HTTP 开发的安全协议。近来年,SSL 被 TLS(即传输层安全协议)所取代。尽管 “SSL” 这个名称仍然被广泛使用,实际上现在 SSL 握手改成是 TLS 握手才是更符合实际的。
让我们通过下面的演示来看看这个过程:
到这一步,还没有实际的内容或应用程序数据被传输。服务器只是确认了连接并建立了一个安全通道。浏览器(客户端)在幕后处理所有这些协商过程。只有在 TLS 握手完成并且双方都交换了 “Finished” 消息之后,他们才能开始安全地发送和接收加密内容。
HTTP 请求/响应周期
现在浏览器请求网站的内容。服务器处理这个请求并发送回一个响应。这就是 HTTP 请求/响应周期发挥作用的地方。后端开发人员负责处理请求,而前端开发人员更关注这个周期的响应部分。
观看下面的演示来可视化这个周期(HTTP 请求流):
我们在上面的演示中看到了一个叫做 “TTFB” 的东西。TTFB 代表首次字节时间(Time To First Byte)。它是客户端请求发送到服务器之后、服务器发送响应返回、直到客户端接收到第一个字节时,这中间所耗费的时间。它是衡量服务器响应时间和网站性能的一个重要指标。较低的 TTFB 表示服务器响应更快。
提示:良好的 TTFB 值应该是 0.8 秒或更少,大于 1.8 秒就属于较差的了。介于两者之间的值属于 “需要改进”。
正如你在演示中看到的,在收到第一个字节后,响应继续加载。服务器仍在处理请求并将剩余数据发送回客户端。从这里,浏览器开始介入处理响应。
标记化(Tokenization)
浏览器将开始解析这个 HTML 响应。它读取 HTML 的原始字节,并根据文件指定的编码(如 UTF-8)将它们转换为单个字符。以下是原始字节可能的一个样子:
01001000 01010100 01001101 01001100
提示:每组八个比特代表一个字节,每个字节可以根据编码标准转换为一个字符、符号或指令。例如,在 UTF-8 编码中,字母 A 的二进制序列是 01000001,字母 B 则是 01000010。
浏览器将字符串字符转换为不同的标记(tokens)并对它们进行分组,例如,<html>
、<body>
以及尖括号内的其他字符串,每个标记都是一个有意义的单元,代表一个 HTML 组件。
HTML 标记化 Demo:
DOM 树创建
DOM(文档对象模型)树是 HTML 文档结构的层次表示。它将元素表示为节点树,每个节点代表文档中的一个元素、属性或文本内容。:
作为参考,这是我们收到的响应定义成一个简单的 HTML 文档,类似于下面这样:主要有一个 h1 标签、一个 p 标签、一个 a 标签和一点 CSS 样式。
<html lang="en-US"><head><style>.heading {color:#0099ff; font-size: 14px}p {margin: 0.5em 0;}a {color: #0099ff; text-decoration: underline;}</style></head><body><h1 class="heading">My Page</h1><p>A paragraph with a <a href="https://example.com/about">link</a></p></body>
</html>
DOM 是一个树状结构,所以可以很容易的表示父子关系。
每次浏览器渲染网页时,它都会经历这个多步骤的过程:将 HTML 字节解析为字符,识别标记,将它们转换为节点,最后构建 DOM 树。虽然 DOM 树定义了 HTML 元素的结构和关系,但它并不决定它们的视觉外观。这是由接下来要讲的 CSSOM 决定的。
CSSOM 树创建
CSS 对象模型(CSSOM)是应用于 HTML 文档的 CSS 样式的表示,它类似 DOM 树,但表示的是 CSS 样式而不是 HTML 结构。CSSOM 用于计算文档中每个元素的最终样式。
查看这个演示来了解 CSSOM 树是如何创建的:
浏览器将这些样式解析为一个精简、内存高效且优化的数据结构,旨在有效地组织样式规则,允许根据匹配的选择器快速检索和应用样式。
一旦构建了 DOM 和 CSSOM,浏览器就可以开始创建渲染树了。
渲染树创建
渲染树是 DOM 和 CSSOM 树的组合,它表示页面的视觉结构,包括布局和样式信息。渲染树用于计算布局并在屏幕上绘制元素。
从上面的演示中可以看到,渲染树是 DOM 和 CSSOM 树的组合。它只包含将在屏幕上显示的元素。它为每个元素计算了样式和布局信息,允许浏览器准确地渲染页面。这就把我们带到了渲染过程的最后一个阶段——布局。
布局
你一定使用 CSS 做过定义过不同 “布局“。在浏览器渲染场景, “布局“这个术语,表示计算页面上每个元素的确切位置和大小的过程。
布局过程涉及根据渲染树确定来每个元素的尺寸、边距、填充、边框和定位。你可能见过元素相互重叠,例如模态窗口、下拉菜单等,这就是布局过程发挥作用的地方。
即使 HTML 标记是有序的,由于 CSS 属性会改变布局,元素也不一定按照它们编写的顺序出现。让我们看看这些属性如何影响布局。
写在 HTML 代码里的 DOM 顺序不会改变,但当我们使用 CSS 属性改变布局时,布局过程必须参与处理。当然,其他一些 CSS 属性,诸如 position、display、top、left 和 z - index,同样会影响布局,从而触发布局过程。
从上面的演示中看到的,元素的流受到它们的显示类型(display
)的影响:块元素垂直堆叠,而内联元素水平流动;而定位属性如 static, relative, absolute, fixed 和 sticky 则进一步决定了元素如何相互排列——例如,相对定位使元素偏离其正常位置而不影响周围的布局,而绝对定位使元素完全脱离文档流。
经过布局这一步骤,就得到了一个显示每个元素的精确计划。接下来就进入到绘制步骤了,在这个步骤中 HTML 文档最终以视觉形式被渲染到屏幕上。
绘制
在绘制阶段,浏览器将从上一步拿到的结构化布局信息翻译出来,将页面中的每个元素绘制到屏幕上。这个过程设计填充颜色、应用图片、边框、阴影和其他视觉样式,绘制顺序基于堆叠上下文(stacking context),确保元素根据 z-index
和其他属性正确分层。
绘制阶段演示 Demo:
绘制过程也是会优化的,以尽量减少需要重新绘制的像素数量。浏览器使用分层(layering)、合成(compositing)和缓存(caching )等技术来提高页面渲染效率。
拓展内容
JavaScsript 扮演的角色
本文中没有讨论浏览器如何解析和优化 JavaScript,这是一个完全不同的故事。简单说,它会在初始渲染后操作 DOM 和 CSSOM,创建动态和交互式的网页体验。
浏览器渲染引擎
不同的浏览器使用不同的渲染引擎,如 Blink(Chrome)、WebKit(Safari)和 Gecko(Firefox)。这些引擎可能具有不同的性能特征和兼容级别。某些 CSS 功能或 JavaScript 方法可能在一个引擎中支持,但在其他引擎中不支持,或者它们的行为可能不一致。此外,不同的引擎对 HTML 元素所应用的默认样式也可能有所差异。
总结
最后,总结浏览器渲染过程的所有步骤:
-
DNS 查找:当输入 URL 时,浏览器执行 DNS 查找,将域名转换为 IP 地址,以便定位到网站服务器
-
TCP/TLS 握手:浏览器发起 TCP 握手与服务器建立连接。如果网站是基于 https 的,还会进行 TLS 握手来加密传输的数据
-
HTTP 请求/响应周期:建立连接后,浏览器发送一个 HTTP 请求获取网站的内容,服务器响应以必要的 HTML、CSS、JavaScript 或其他其他资源
-
标记化:浏览器将 HTML 响应的原始数据,将其转换为单个字符,然后转换为标记(如
<html>
、<body>
) -
DOM 树创建:浏览器构建 DOM(文档对象模型)树,这是 HTML 文档结构的层次表示,每个节点代表一个元素或文本内容
-
CSSOM 树创建:浏览器解析 CSS 并创建 CSSOM(CSS 对象模型)树,它表示与 HTML 文档元素相关联的样式
-
渲染树创建:DOM 和 CSSOM 树组合形成渲染树,这是页面布局的视觉表示,只包括可见元素及其计算样式
-
布局:浏览器根据 CSS 属性(如边距、填充和定位(如 static、absolute))计算屏幕上每个元素的确切大小和位置
-
绘制:浏览器基于渲染树将像素绘制到屏幕上,填充颜色、图片、边框和阴影,这些都是由 CSS 定义的