程序常常被划分为几个相互独立又彼此配合的模块,浏览器也是如此,以 Chrome 为例,它由多个进程组成,每个进程都有自己核心的职责,它们相互配合完成浏览器的整体功能,每个进程中又包含多个线程,一个进程内的多个线程也会协同工作,配合完成所在进程的职责。
当启动一个应用时,计算机会创建一个进程,操作系统回味该进程分配一部分内存,应用的所有状态都会保存在这块内存中,应用还会创建很多个线程来辅助工作,这些线程可以共享这部分内存中的数据,应用关闭,进程也将终止,操作系统随之会释放这部分内存。
DNS 查询是指从用户的计算机发送到 DNS 服务器的请求,以获取与域名关联的 IP 地址。DNS 查询涉及多个服务器,包括本地、根和顶级域名服务器,有两种类型的 DNS 服务器:权威和递归。当用户键入域名时,DNS 客户端将发送一个请求到 DNS 服务器,包含一个完整的域名和指定的查询类型和类。DNS 服务器可以直接解析查询,也可以联系其他 DNS 服务器递归地解析它。DNS 查询被缓存,但查询在缓存中停留的时间由 TTL 确定。如果 DNS 服务器没有及时提供答案,它会减慢一切速度,如果权威 DNS 服务器崩溃,那么查询最终会超时并且不会提供答案。
- 查找浏览器缓存:浏览器会缓存 2-30 分钟访问过网站的 DNS 信息,如果没有找到
- 检查系统缓存:检查 hosts 文件,它保存了一些访问过网站的域名和 IP 的数据,如果没有找到
- 检查路由器缓存:路由器有自己的 DNS 缓存,如果没有找到
- 检查 ISP DNS 缓存:ISP 服务商 DNS 缓存(本地服务器缓存),如果没有找到
- 递归查询:从根域名服务器到顶级域名服务器再到极限域名服务器依次搜索对应目标域名的 IP
一旦浏览器知道了网站的 IP 地址,它将尝试通过 TCP 三次握手(也称为 SYN-SYN-ACK,或者更准确的说是 SYN、SYN-ACK、ACK,因为 TCP 有三个消息传输,用于协商和启动两台计算机之间的 TCP 会话),与持有资源的服务器建立连接。
第一次握手:客户端向服务器端发送请求等待服务器确认
第二次握手:服务器收到请求并确认,回复一个指令
第三次握手:客户端收到服务器的回复指令并返回确认
对于通过 HTTPS 建立的安全连接,需要进行 TLS 协商握手,以此来决定用哪个密码加密通信,验证服务器,并在开始实际的数据传输之前建立一个安全的连接。
TLS(Transport Layer Security)是一种安全协议,用于提供互联网连接的安全性和数据完整性。TLS 协议在网络传输层上加密数据,以保护互联网连接。TLS 使用 SSL(Secure Sockets Layer)协议来加密网络连接。TLS 和 SSL 协议都用于加密数据和验证连接,以确保在互联网上传输数据时的安全性和隐私性。在 TLS 握手协商过程中,客户端和服务器通过交换和验证数字证书来建立安全连接。这些数字证书包含公钥和其他信息,用于证明服务器的身份并加密数据。TLS 协议提供更高级别的安全特性,并是 SSL 协议的更新版本。TLS 协议是加密电子邮件和确保隐私和安全传递的标准互联网协议。
完全所有握手后,便建立了连接,接下来就可以发送请求获取数据了
HTTP 请求
建立连接后浏览器会发送 HTTP 请求
在真正发起网络请求之前,浏览器会先在浏览器缓存中查询是否有要请求的文件。其中,浏览器缓存是一种在本地保存资源副本,以供下次请求时直接使用的技术。
当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载。这样做的好处有:
缓解服务器端压力,提升性能(获取资源的耗时更短了);
对于网站来说,缓存是实现快速资源加载的重要组成部分。
当然,如果缓存查找失败,就会进入网络请求过程了。
请求方法 - POST, GET, PUT, PATCH, DELETE 等
URI - 是统一资源识别符的缩写。URIs 用于识别互联网上的抽象或物理资源,如网站或电子邮件地址等资源。一个 URI 最多可以有 5 个部分
scheme:用于说明使用的是什么协议
authority:用于识别域名
path:用于显示资源的确切路径
query:用于表示一个请求动作
fragment:用来指代资源的一部分
HTTP 头字段 - 是浏览器和服务器在每个 HTTP 请求和响应中发送和接收的字符串列表(它们通常对终端用户是不可见的)。在请求的情况下,它们包含关于要获取的资源或请求资源的浏览器的更多信息。
如果你想看看这些请求头字段是什么样子的,请进入 Chrome 浏览器并打开开发者工具(F12)。进入 Network 标签,选择 FETCH/XHR。在下面的屏幕截图中,我刚刚在搜索引擎上搜索了 Palm Springs,这就是请求头的样子。
HTTP 响应
服务器收到请求,它会处理并回复一个 HTTP 响应。在响应的正文中,我们可以找到所有相关的响应头和请求的 HTML 文档的内容
常见响应状态码:
分类 | 描述 |
---|---|
1** | 信息。服务器收到请求,需要请求者继续执行操作 |
2** | 成功。操作被成功接收并处理 |
3** | 重定向。需要进一步的操作以完成请求 |
4** | 客户端错误。请求包含语法错误或无法完成请求 |
5** | 务器错误。服务器在处理请求的过程中发生了错误 |
成功收到 200 响应后,就会获取对应的 HTML 文档,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/src/static/favicon.ico" type="image/x-icon">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>xxx</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
当浏览器收到 HTML 文档后,会产生一个渲染任务,并将其任务传递给渲染主线程的消息队列中,在事件循环机制下,渲染主线程取出消息队列中的任务,并执行渲染工作流
当渲染进程收到渲染任务就会开展渲染工作流
拿到HTML文档后会对HTML进行词法分析,进行逐个字分析,进行词的分类,然后把每个词添加进 token 数组中,进行身份标识
词法分析(lexical analysis):进行词法分析的程序或者函数叫作词法分析器(Lexical analyzer,简称 Lexer),也叫扫描器(Scanner,例如 typescript 源码中的 scanner.ts),字符流转换成对应的 Token 流。
tokenize:tokenize 就是按照一定的规则,例如 token 令牌(通常代表关键字,变量名,语法符号等),将代码分割为一个个的“串”,也就是语法单元)。涉及到词法解析的时候,常会用到 tokennize。
语法分析(parse analysis):是编译过程的一个逻辑阶段。语法分析的任务是在词法分析的基础上将单词序列组合成语法树,如“程序”,“语句”,“表达式”等等.语法分析程序判断源程序在结构上是否正确。源程序的结构由上下文无关文法描述。
理论上讲非常的抽象,实际上代码的编译过程翻译成人话就是,先从这一团杂乱的字符中提取关键信息,其中包含:关键字、单词、符号和分隔符等等,并将这些关键信息整理成一张线性表。得到线性表后的计算机程序会根据得到的关键词和符号等结构将程序整理成有规则的结构,然后将关键信息放在置顶的关键位置,这个结果通常是一棵树也被称为 AST(abstract syntax tree)抽象语法树。这个过程进行完毕浏览器就成功的将 HTML 转换成了 DOM 树。
如下例子:
<div calss="root">test</div>
一般使用 状态机 (简单来说就是每获取一个字符,就根据上一次状态和输入的字符,来转换一次状态,直到获得一个 token)
// 字节流数组
[
{
"text": "<",
"type": "startLess",
"line": 1,
"start": 0,
"end": 1
},
{
"text": "div",
"type": "nodeName",
"line": 1,
"start": 1,
"end": 4
},
{
"text": "class",
"type": "attributeName",
"line": 1,
"start": 5,
"end": 10
},
{
"text": "=",
"type": "attrubuteEqual",
"line": 1,
"start": 10,
"end": 11
},
{
"text": "\"root\"",
"type": "attributeValue",
"line": 1,
"start": 11,
"end": 20
},
{
"text": ">",
"type": "startGreater",
"line": 1,
"start": 21,
"end": 22
},
{
"text": "test",
"type": "text",
"line": 1,
"start": 23,
"end": 27
},
{
"text": "</",
"type": "endLess",
"line": 1,
"start": 28,
"end": 30
},
{
"text": "div",
"type": "nodeName",
"line": 1,
"start": 31,
"end": 35
},
{
"text": ">",
"type": "endGreater",
"line": 1,
"start": 36,
"end": 37
},
]
构建 DOM 树
有了词法分析器构造的 token 线性表之后,下一步的任务就是通过遍历线性表识别出 HTML 应用的节点层级关系并将其填装到一个对象中,这个对象就是 DOM 树对象
为了提高解析效率,浏览器会启动一个预解析线程率先下载 HTML 中的外部 CSS 文件 JS 文件,如果主线程解析到 link 位置,外部的 CSS 文件还没下载解析好,主线程不会等待,会继续解析后续的 HTML,因为下载和解析 CSS 的工作是在预解析线程中进行的,所以 CSS 不会阻塞 HTML 解析的根本原因