网页之旅🛣️ - 浏览器的工作原理

2025-05-24

网页之旅🛣️ - 浏览器的工作原理

本文介绍了用户在浏览器中输入网址和网页显示之间的过程。

概述

从用户请求网页到网页在浏览器中显示,需要经过几个步骤。为了更好地讲解每个步骤,我将这些步骤分为以下几个部分:

  1. 导航
    • 解析网址(DNS 查找)
    • 建立与服务器的连接(TCP 三次握手)
    • 建立安全协议(TLS 协商)
  2. 获取
    • HTTP 请求
    • HTTP 响应
  3. 解析
    • 构建 DOM 树
    • 构建 CSSOM 树
    • 将树组合成渲染树
    • 预载扫描仪
    • JavaScript 编译
    • 构建可访问性树
  4. 渲染
    • 关键渲染路径
    • 布局
    • 合成
  5. 最终完成
    • JavaScript 已被占用。
    • 用户现在可以浏览该页面!

使用此索引可跳转到任何感兴趣的部分。如果您对某些概念还不熟悉,我建议您先阅读全文。现在是时候快速了解一下背景知识了!

背景

本节包含理解本文后续部分所需的一些核心背景概念的非常快速概括的概述。

网络模型

存在一些模型来解释数据如何在网络中传输。特别是,有一个广为人知的网络模型,即使不参与黑客活动的人也可能听说过!我们称之为开放系统互连(OSI)模型。

OSI模型

OSI模型

开放系统互连 (OSI) 模型描述了计算机系统用于网络通信的七层结构。每一层都比前一层抽象程度高一级,一直到我们将要讨论的应用程序(浏览器)层。

重要的是要理解,OSI 模型是应用程序如何在网络上通信的“概念模型”。它不是协议。不要混淆这两者。协议是存在于这些层级中的一组严格的规则。

TCP/IP 模型是一个更古老且非常相似的模型,可能与本文更相关。该网络模型既用于建模当前的互联网架构,也提供了一套规则(具体协议),所有通过该网络进行的传输都应遵循这些规则。

OSI 与 TCP/IP

我将在整篇文章中使用和引用该模型及其相关协议。

任何数据从一个应用程序发送到另一个应用程序,都必须在这些层之间来回传输几次(取决于中间商的数量)。当然,如今这种情况发生得非常快,然而,它仍然会发生,并且了解这个过程的概况是每个开发人员都应该知道的。以下是服务器和客户端应用程序之间的网络过程的图像表示:

应用程序之间的 TCP/IP 模型数据路径

举个例子——一个用户请求使用他们的浏览器浏览一个页面:

  1. 请求首先被发送到应用层,在那里逐层处理,每层执行其指定的功能。
  2. 然后,数据通过网络的物理层传输,直到目标服务器或其他设备接收它。
  3. 此时,数据再次通过各层传递,每一层执行其指定的操作,直到数据被 Web 服务器软件使用。
  4. 这个过程再次重复,直到服务器做出响应。

一般来说,这就是机器通过网络进行通信的方式!

浏览器的高级抽象

本文的后续部分将介绍典型的浏览器如何在屏幕上显示页面内容。阅读这些部分时,对浏览器有深入的了解非常重要。我将参考以下一些浏览器组件:

  1. 用户界面:包括地址栏、后退/前进按钮、书签菜单等。除了您看到请求页面的窗口之外,浏览器显示的每个部分。
  2. 浏览器引擎:协调 UI 和渲染引擎之间的操作。
  3. 渲染引擎:负责显示请求的内容。例如,如果请求的内容是 HTML,则渲染引擎会解析 HTML 和 CSS,并将解析后的内容显示在屏幕上。
  4. 网络:对于 HTTP 请求等网络调用,在平台无关的接口背后针对不同的平台使用不同的实现。
  5. UI 后端:用于绘制组合框和窗口等基本控件。此后端公开一个通用接口,该接口不特定于平台。其底层使用操作系统的用户界面方法。
  6. JavaScript解释器:用于解析和执行JavaScript代码。
  7. 数据存储:这是一个持久层。浏览器可能需要在本地保存各种数据,例如 Cookie。浏览器还支持 localStorage、IndexedDB、WebSQL 和 FileSystem 等存储机制。

浏览器组件

值得注意的是,出于性能和安全方面的考虑,Chrome 等浏览器采用了多进程设计。这意味着它们会为每个标签页(每个标签页都是一个独立的进程)运行某些组件(例如渲染引擎)的实例。您可以通过在任务管理器中检查 Chrome 的进程来证明这一点。

Chrome 任务管理器的屏幕截图

从上面的截图中可以看出,每个选项卡都有进程优先级和 CPU/网络/GPU 统计信息,这意味着它们像正常进程一样工作。因为它们确实如此!您可以通过列出操作系统的进程来进一步确认这一点,您肯定能在那里找到它们。

总结一下背景知识部分,需要注意的是,您刚才阅读的内容是对网络和浏览器实际工作原理的非常高层次的概括和抽象。并非所有网络都严格遵循 OSI/TCP/IP 模型,并且当今使用的主流浏览器各有不同,但它们共享一个共同的概念基础,这使得我们能够将它们抽象成您刚才阅读的内容。

  • 以浏览器为例,它们都(在一定程度上)遵循由万维网联盟(W3C)维护的规范,该组织是 Web 的标准组织。然而,每个组件的实际实现差异很大。例如,渲染引擎各不相同,Internet Explorer 使用 Trident,Firefox 使用 Gecko,Safari 使用 WebKit。Chrome、Edge 和 Opera 使用 Blink(WebKit 的一个分支)等等。JavaScript 引擎管道也是如此!

页面之旅

你打开浏览器,输入www.google.com。接下来会发生以下情况。

导航

第一步是导航到正确的位置。导航到网页就是找到该页面资源所在的位置。对我们来说,网页只是域名,但对计算机来说,它们会被解析成 IP 地址。

<-> IP

如果您访问www.google.com,页面资源将位于 IP 地址为 93.184.216.34 的服务器上。如果您从未访问过此网站,则必须进行域名系统 (DNS) 查找。

往返时间

往返时间 (RTT)是指浏览器从发送请求到收到服务器响应的时长,以毫秒为单位。它是 Web 应用程序的一个关键性能指标,也是衡量页面加载时间和网络延迟的主要因素之一,与首字节时间 (TTFB)一样。

我将用相应的 RTT 注释每个网络进程。

解析网址 - DNS 流程 (O RTT)

DNS 流程概述www.google.com如下:

  1. 检查浏览器和操作系统缓存,如果找到 IP 则返回。
  2. 浏览器发出请求,询问 DNS 解析器。
    1. DNS 解析器检查其缓存,如果找到 IP 则返回。
  3. DNS 解析器发出询问根名称服务器的请求。
  4. 根域名服务器使用 TLD 域名服务器(在本例中为 扩展的 TLD .com)的 IP 地址来响应 DNS 解析器。
  5. DNS 解析器现在向 TLD 名称服务器发送另一个请求,询问他们是否知道 IP 是什么。
  6. TLD 名称服务器使用权威名称服务器的 IP 向 DNS 解析器做出响应。
  7. DNS 解析器向权威名称服务器发送最终请求,询问 IP。
  8. 权威名称服务器将扫描区域文件以查找域名:ip地址映射,并将其是否存在返回给 DNS 解析器。
  9. 最后,DNS 解析器将向浏览器返回浏览器尝试通信的服务器的 IP。

DNS 流程的可视化

请注意,由于层层缓存,这个过程通常非常快,而且很少能完整执行。它本来就设计得很快!

建立与服务器的连接 - TCP 握手(1 RTT)

现在知道了 IP 地址,浏览器通过TCP 三次握手建立与服务器的连接

TCP使用三次握手来建立可靠的连接。该连接是全双工的,双方相互同步 (SYN) 并确认 (ACK)。这四个标志的交换分为三个步骤:SYN、SYN-ACK 和 ACK,如图所示。

TCP 三次握手

  1. 客户端选择一个初始序列号,设置在第一个 SYN 数据包中。
  2. 服务器还选择自己的初始序列号。
  3. 每一方都通过增加序列号来确认对方的序列号;这就是确认号。

连接建立后,通常会在每个数据段后发送 ACK。连接最终会以 RST(重置或断开连接)或 FIN(正常结束连接)结束。

这种机制的设计使得两个试图通信的实体(在本例中为浏览器和 Web 服务器)可以在传输数据之前协商网络 TCP 套接字连接的参数,在我们的例子中,它将通过 HTTPS(HTTP的安全版本)进行传输。

HTTPS 是加密的 HTTP。这两个协议的唯一区别在于,HTTPS 使用TLS (SSL)来加密普通的 HTTP 请求和响应。因此,HTTPS 比 HTTP 提供了更坚实的安全保障。使用 HTTP 的网站HTTP://的 URL 中包含 ,而使用 HTTPS 的网站的 URL 中包含HTTPS://

建立安全协议 - TLS 协商(~2 RTT)

对于通过 HTTPS 建立的安全连接,需要进行另一次“握手”。这次握手,或者更确切地说是 TLS 协商,决定使用哪种密码来加密通信,验证服务器,并在开始实际数据传输之前确认已建立安全连接。

现代 TLS 建立

虽然确保连接安全会增加页面加载时间,但安全连接值得付出延迟代价,因为浏览器和 Web 服务器之间传输的数据通常无法被第三方解密。TLS 已经取得了长足的进步,1.3 版及以上版本已将往返时间 (RTT) 从 4 降至 2,甚至根据具体情况降至 1。

假设 DNS 是即时的,并添加 HTTP 获取 RTT(下一节),那么浏览器开始显示页面之前需要 4 次往返。如果您正在访问最近连接过的网站,则可以使用 TLS会话恢复功能将TLS 握手阶段从 2 次往返缩短为 1 次。

  • 新连接:4 RTT + DNS
  • 恢复连接:3 RTT + DNS

获取

现在我们已经建立了 TCP 连接,并且 TLS 交换也已完成,浏览器现在可以开始获取页面资源了。它首先会获取页面的标记文档。这通过使用HTTP 协议来实现。HTTP 请求通过 TCP/IP 协议发送,在我们的例子中,HTTP 请求使用了传输层安全性 (TLS) 加密——因为 Google 使用 HTTPS。

HTTP 请求

要获取页面,需要发出幂等(不改变服务器状态)请求。我们使用 HTTP GET 请求。

还有许多其他类型的 HTTP 方法:

根据规范的 HTTP 方法

为了获取我们的页面,我们只对感兴趣GET



GET / HTTP/2
Host: www.google.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
TE: Trailers


Enter fullscreen mode Exit fullscreen mode

HTTP 响应

一旦 Web 服务器收到请求,它就会解析并尝试执行。我们假设请求有效且文件可用。它会返回一个 HTTP 响应,并将相关的 header 和请求的 HTML 文档内容附加到响应结构的 body 中。



HTTP/2 200 OK
date: Sun, 18 Jul 2021 00:26:11 GMT
expires: -1
cache-control: private, max-age=0
content-type: text/html; charset=UTF-8
strict-transport-security: max-age=31536000
content-encoding: br
server: gws
content-length: 37418
x-xss-protection: 0
x-frame-options: SAMEORIGIN
domain=www.google.com
priority=high
X-Firefox-Spdy: h2


Enter fullscreen mode Exit fullscreen mode

*HTML 文档的源代码将位于响应主体中。

解析

浏览器收到响应后,就可以开始解析收到的信息。解析是浏览器将通过网络接收的数据转换为 DOM 和 CSSOM 的步骤,渲染器将使用这些数据将页面绘制到屏幕上。

文档对象模型(DOM)是构成浏览器刚刚收到的标记文档(在本例中为 HTML)的结构和内容的对象的内部表示。它表示页面,以便程序可以更改文档的结构、样式和内容。

DOM 将文档表示为节点和对象。这样,编程语言就可以连接到页面。DOM 树中有许多不同类型的节点。DOM节点接口规范的一个示例部分如下所示:

  • Node.ELEMENT_NODE
  • Node.ATTRIBUTE_NODE
  • Node.TEXT_NODE
  • Node.CDATA_SECTION_NODE
  • Node.PROCESSING_INSTRUCTION_NODE
  • Node.COMMENT_NODE
  • Node.DOCUMENT_NODE
  • Node.DOCUMENT_TYPE_NODE
  • Node.DOCUMENT_FRAGMENT_NODE
  • Node.NOTATION_NODE

这些节点类型涵盖了文档中所有可能的节点。这样的表示有助于编程语言连接到页面,从而对其进行操作。

为了结束我们的解析前言,我们需要讨论一下 CSSOM。

CSS 对象模型(CSSOM)是一组允许通过 JavaScript 操作 CSS 的 API。它与 DOM 非常相似,但针对的是 CSS 而非 HTML。它允许用户动态地读取和修改 CSS 样式。CSSOM 的表示形式与 DOM 非常相似,都是一棵树,它会与 DOM 一起构成渲染树,以便浏览器开始渲染过程。让我们通过整个流程来了解如何操作。

构建 DOM 树

第一步是处理 HTML 标记并构建 DOM 树。HTML 解析涉及标记化和树的构建。

HTML 在解析领域可能令人感到意外,因为它无法通过常规方式进行解析,也无法用上下文无关语法 (CFG)进行定义。相反,有一种用于定义 HTML 的正式格式,称为文档类型定义 (DTD)。我不会过多地介绍其实现方式,但主要原因如下:

  • 语言的宽容本质。
  • 事实上,浏览器具有传统的容错能力,可以支持众所周知的无效 HTML 情况。
  • 解析过程是可重入的。对于其他语言,源代码在解析过程中不会发生变化,但在 HTML 中,动态代码(例如包含document.write()调用的脚本元素)可以添加额外的标记,因此解析过程实际上会修改输入。

由于无法使用常规解析技术,浏览器会创建自定义解析器来解析 HTML。

HTML5 规范对解析算法进行了详细描述。如前所述,该算法包含两个阶段:标记化和树构建。

  • 标记化是词法分析,将输入解析为标记。HTML 标记包括开始标签、结束标签、属性名和属性值。
  • 树的构建本质上是创建一个基于解析的标记的树,也是我们将要关注的 DOM 树。

DOM 树描述了文档的内容。<html>元素是文档树的第一个标签和根节点。树反映了不同标签之间的关系和层次结构。嵌套在其他标签中的标签是子节点。DOM 节点数量越多,构建 DOM 树所需的时间就越长。以下是 DOM 树的可视化表示——解析器的输出:

DOM 树的一部分

当解析器发现非阻塞资源(例如图片)时,浏览器会请求这些资源并继续解析。解析过程在遇到 CSS 文件时可以继续,但<script>某些标签(尤其是没有 async 或 defer 属性的标签)会阻塞渲染,并暂停 HTML 的解析。虽然浏览器的预加载扫描器可以加快这一过程,但过多的脚本仍然会成为严重的瓶颈。

构建 CSSOM 树

第二步是处理 CSS 并构建 CSSOM 树。与 DOM 解析阶段类似,浏览器会遍历 CSS 中的每个规则集,并根据 CSS 选择器创建具有父级、子级和兄弟级关系的节点树。

就解析而言,与 HTML 不同,CSS 是一种上下文无关语法,可以使用常规的 CFG 解析技术进行解析。事实上,CSS 规范定义了 CSS 的词法和语法

与 HTML 类似,浏览器需要将接收到的 CSS 规则转换为它能够处理的内容。之后,它会重复 HTML 到对象的过程,只不过这次转换的是 CSS。

将树组合成渲染树

CSSOM 和 DOM 树组合成渲染树,然后用于计算每个可见元素的布局,并作为将像素渲染到屏幕的绘制过程的输入。

结合 CSSOM + DOM 树来构建渲染树

为了构建渲染树,浏览器大致执行以下操作:

  1. 从 DOM 树的根开始,遍历每个可见节点。
    • 某些节点不可见(例如,脚本标签、元标签等),并且由于它们未反映在渲染的输出中而被省略。
    • 一些节点通过 CSS 隐藏,并且也从渲染树中省略;例如,上面示例中的 span 节点在渲染树中缺失,因为我们有一个明确的规则在其上设置了“display: none”属性。
  2. 对于每个可见节点,找到适当的匹配 CSSOM 规则并应用它们。
  3. 发出带有内容及其计算样式的可见节点。

最终输出的是包含屏幕上所有可见内容的内容和样式信息的渲染图。渲染树完成后,我们就可以进入“布局”阶段了。

预载扫描仪

当浏览器的主线程忙于构建 DOM 树时,它会有一个辅助工作线程扫描可用的内容。这个辅助线程就是预加载扫描器,它会为 CSS、JavaScript 和 Web 字体等资源准备高优先级的获取请求。这是在解析阶段添加的一项优化,因为解析器查找这些请求的引用会花费太长时间。



<link rel="stylesheet" src="styles.css"/>
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="image description"/>
<script src="anotherscript.js" async></script>


Enter fullscreen mode Exit fullscreen mode

以上面的例子为例,预加载扫描器会尝试查找脚本和图片,并开始下载它们。有一些方法可以通过 HTML 属性与预加载扫描器通信:asyncdefer

  • async:当存在时,它指定脚本一旦可用就会异步执行。
  • defer:如果存在,则指定在页面完成解析时执行脚本。

等待获取 CSS 不会阻止 HTML 解析或下载,但它会阻止 JavaScript,因为 JavaScript 经常用于查询 CSS 属性对元素的影响。

JavaScript 编译

在解析 CSS 并创建 CSSOM 的同时,其他资源(包括 JavaScript 文件)也在下载(这要归功于预加载扫描器)。JavaScript 会被解释、编译、解析并执行。

JS 编译过程概述

脚本会被解析成抽象语法树。如上图所示(来自 V8 引擎博客),一些浏览器引擎会将抽象语法树传入解释器,输出在主线程上执行的字节码。这被称为 JavaScript 编译。

构建可访问性树

浏览器还会构建一个辅助设备用来解析和解释内容的无障碍树。无障碍对象模型 (AOM)类似于 DOM 的语义版本。当 DOM 更新时,浏览器也会更新无障碍树。辅助技术本身无法修改无障碍树。

AOM的构建和使用流程

在 AOM 构建之前,屏幕阅读器无法访问其内容。

渲染

现在信息已经解析完毕,浏览器就可以开始显示了。为了实现这一点,浏览器将使用渲染树来生成文档的可视化表示。渲染步骤包括布局、绘制,在某些情况下还包括合成。

关键渲染路径

现在是时候介绍一下关键渲染路径的概念了。最好的可视化方式是用一张图:

关键渲染路径

优化关键渲染路径可以缩短首次渲染时间。确保重排和重绘能够以每秒 60 帧的速度进行(以实现高性能的用户交互)至关重要。

我们不会详细介绍如何优化 CRP,但总体思路是通过优先加载哪些资源、控制加载顺序以及减少这些资源的文件大小来提高页面加载速度。

现在让我们进入渲染阶段。

布局

布局是渲染的第一个阶段,在此阶段确定渲染树节点的几何形状和位置。渲染树构建完成后,布局就开始了。

布局是一个递归过程。它从根渲染器(对应于<html>HTML 文档的元素)开始。布局过程以递归方式遍历部分或全部框架层次结构,为每个需要几何信息的渲染器计算几何信息。

布局阶段

在布局阶段结束时,会生成一棵类似上面的树,其中我们称之为块/Coxes(盒子)的节点。块/盒子保存着 DOM 对象和节点的几何信息。

脏位系统

为了避免每次细微的改动都进行完整的布局,浏览器使用了“脏位”系统。被更改或添加的渲染器会将自身及其子渲染器标记为“脏”:需要布局。

有两个标志:
-脏:节点需要布局。
-子节点很脏:节点至少有一个子节点需要布局。

布局算法

使用脏位系统,浏览器现在可以执行算法来生成布局。该算法的高级抽象如下:

  1. 父节点决定自己的宽度。
  2. 父母陪孩子一起:
    1. 计算子渲染的大小
    2. 如果子代有脏的后代,则调用子代布局
  3. 父级使用子级的累积高度以及边距和填充的高度来设置自己的高度——这将由父级渲染器的父级使用。
  4. 将其脏位设置为 false。

另一个重要的概念是“回流” 。如前所述,第一次确定节点的大小和位置称为“布局”。后续对节点大小和位置的重新计算称为“回流”。例如,假设初始布局发生在返回图像之前。由于我们没有声明图像的大小,因此一旦知道图像大小,就会发生回流!

渲染的第三阶段,也是最后一阶段。在此绘制阶段,浏览器将布局阶段计算出的每个框转换为屏幕上的实际像素。绘制涉及将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影以及按钮和图像等替换元素。浏览器需要非常快速地完成此操作。

绘画顺序

CSS2 定义了绘制过程的顺序。这实际上是元素在堆叠上下文中的堆叠顺序。由于堆叠顺序是从后向前绘制的,因此此顺序会影响绘制。块渲染器的堆叠顺序为:

  1. 背景颜色
  2. 背景图像
  3. 边界
  4. 孩子们
  5. 大纲
油漆层

绘制可以将布局树中的元素拆分成多个图层。将内容提升到 GPU 上的图层(而不是 CPU 上的主线程)可以提升绘制和重绘的性能。有一些特定的属性和元素可以实例化图层,包括<video><canvas>,以及任何具有 opacity、3D transform、will-change 和其他一些 CSS 属性的元素。这些节点将与其后代一起绘制到各自的图层上,除非后代由于上述一个(或多个)原因需要自己的图层。

层确实可以提高性能,但在内存管理方面成本高昂,因此不应过度使用层作为 Web 性能优化策略的一部分。

合成

当文档的各个部分绘制在不同的层中并相互重叠时,需要进行合成以确保它们以正确的顺序绘制到屏幕上并正确呈现内容。

合成阶段

随着页面继续加载资源,可能会发生回流(回想一下我们之前提到的延迟加载的图片)。回流会引发重绘和重新合成。如果我们定义了图片的大小,就不需要回流,只需要重绘需要重绘的图层,并在必要时进行合成。但我们没有包含图片大小!当从服务器获取图片时,渲染过程会回到布局步骤并从那里重新开始。

最终完成

一旦主线程完成页面绘制,您可能会认为“一切就绪”。但事实并非如此。如果加载包含 JavaScript(该 JavaScript 已正确延迟,并且仅在 onload 事件触发后执行),则主线程可能会很忙,无法进行滚动、触摸和其他交互。

JavaScript 占用

可交互时间 (TTI)衡量的是从第一个请求(导致 DNS 查找和 SSL 连接)到页面可交互所用的时间。可交互是指首次内容绘制 (First Contentful Paint)之后页面在 50 毫秒内响应用户交互的时间点。如果主线程正在解析、编译和执行 JavaScript,则主线程不可用,因此无法及时(少于 50 毫秒)响应用户交互

在我们的示例中,图片加载速度可能很快,但 anotherscript.js 文件可能高达 2MB,而用户的网络连接速度较慢。在这种情况下,用户虽然能很快看到页面,但滚动时可能会卡顿,直到脚本下载、解析并执行完毕。这可不是好的用户体验。请避免占用主线程,正如以下 WebPageTest 示例所示:

示例 - JavaScript 占用主线程

在这个例子中,DOM 内容加载过程花费了超过 1.5 秒的时间,并且主线程在整个时间内都被完全占用,对点击事件或屏幕点击没有响应。

用户现在可以浏览该页面了!🎉

是的,经过所有这些阶段后,用户现在可以看到页面并与之交互!

概括

为了在浏览器窗口中显示一个简单的页面,可能需要经过:

  • DNS 查找:找出网址的 IP。
  • TCP 握手:为后续步骤建立客户端与服务器之间的 TCP/IP 通信。
  • TLS 握手:通过加密来保护将要发送的信息。
  • HTTP通信:建立浏览器可以理解的通信方法。
  • 浏览器解析:解析HTTP的响应——要显示的页面的文档。
  • 浏览器渲染:在浏览器窗口上渲染文档。
  • Javascript 占用:等待 JS 编译并执行,因为它可能会占用主线程。

完成这个看似简单的任务,其中涉及的环节之多令人难以置信。对于我们这个小页面来说,这真是一段令人印象深刻的旅程 :)

还有更多...

在现实世界中,我们的小页面在到达目的地之前可能必须克服更多的障碍,例如:负载平衡器、代理多层防火墙等......

这些主题本身就很复杂,如果你对它们的工作原理感兴趣,我建议你去查一下!也许以后我会写点相关的内容。

谢谢你的阅读,
Paulo😎

参考

文章来源:https://dev.to/gitpaulo/journey-of-a-web-page-how-browsers-work-10co
PREV
如何安全地存储 JWT 令牌。
NEXT
为什么每个初创公司都应该建立在开源之上……🤔🤔