简单了解计算机组成
- CPU
中央处理, 用来处理计算机中的各种指令集,确保计算的准确性和系统的稳定性, 他是计算机的核心
- GPU(显卡)
图像处理器, 和 CPU 类似, 但是他的功能更加的单一, 专门用来处理计算机算机中图像相关的信息
- 内存条
在计算机中用于暂时存储信息, cpu 可以以非常快的速度访问内存中的数据
- 固态磁盘
就是一个存储器, 用于存储数据
- 操作系统
操作系统是控制和管理整个计算机系统的硬件和软件资源,合理有效地组织计算机工作和资源分配的软件集合
...
他们之间的关系:电脑开机时, 操作系统的一些文件会从固态磁盘中加载到内存中, cpu 便可以执行, 在执行过程中如果发现有图像相关的信息, 便会将数据传递给显卡, 显卡处理后再交给显示器展示, 如果遇到音频信息, 会交给声卡处理,最后通过外设播放。
同理一个软件的执行大致也是这个流程
进程与线程
什么是进程:
进程是程序在计算机上的一次执行过程,是系统进行资源分配和调度的基本单位
什么是线程:
线程是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运作单位。
一个进程可以理解成一个空间, 真正执行代码的是进程中的一个线程, 一个进程至少有一个线程
理解进程:
理解线程:
浏览器有哪些进程和线程?
浏览器是一个多进程和多线程的应用程序
浏览器内部工作极其复杂, 在浏览器启动后会自动启动多个进程。
其中主要的进程有:
-
浏览器进程
主要负责界⾯显示、⽤户交互、⼦进程管理等。浏览器进程内部会启动多个线程处理不同的任务。
-
⽹络进程 负责加载⽹络资源。⽹络进程内部会启动多个线程来处理不同的⽹络任务。
-
渲染进程(本节课重点讲解的进程)
渲染进程启动后,会开启⼀个渲染主线程,主线程负责执⾏ HTML、CSS、JS 代码。
默认情况下,浏览器会为每个标签⻚开启⼀个新的渲染进程,以保证不同的标签⻚之间不相互影响。
输入一个网址后发生了什么
-
DNS解析
解析域名时, 会先查找本地缓存的对应 ip 地址, 如果找到, 通过 tcp/ip 协议去寻找对应的资源, 如果没找到, 会先通过 udp 协议发送一个请求报文去查询对应的 ip 地址,找到地址后通过 tcp/ip 协议去请求对应静态资源服务器中的资源
浏览器会先到本地域名服务器,递归查找域名对应的 ip
找不到,就会进行一个迭代查询,根域名服务器查找域名对应的 ip
再找不到,就到顶级域名服务器查找域名对应的 ip
还找不到,就到目标域名服务器查找域名对应的 ip
找到后,浏览器将 ip 写入 DNS 缓存,方便下次直接在本地查找的到这个域名对应的 ip
-
TCP/IP
找到 ip 地址后,会向指定服务器发起 http 请求, 请求信息以报文的形式发送个服务端, 但会先和指定服务器建立链接, TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。同时由于TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议,TCP是全双工模式,所以需要四次挥手关闭连接。
网络数据传输过程
三次握手
-
第一次握手
客户端将TCP报文标志位SYN置为1,随机产生一个序号值seq=J,保存在TCP首部的序列号(Sequence Number)字段里,指明客户端打算连接的服务器的端口,并将该数据包发送给服务器端,发送完毕后,客户端进入
SYN_SENT状态,等待服务器端确认。 -
第二次握手
服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将TCP报文标志位SYN和ACK都置为1,ack=J+1,随机产生一个序号值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入
SYN_RCVD状态 -
第三次握手
客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入
ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
注意:ack和ACK,不是同一个概念:
- 小写的 ack 代表的是头部的确认号 Acknowledge number, 缩写 ack,是对上一个包的序号进行确认的号,ack=seq+1。
- 大写的 ACK,则是 TCP首部的标志位,用于标志的 TCP 包是否对上一个包进行了确认操作,如果确认了,则把ACK 标志位设置成 1。
获取资源
建立完连接后, 浏览器正式向指定服务器发送 http 协议请求, 这时候会走 TCP/IP 的五层协议,在此之前我们需要明确理解 OSI 的七层协议和 TCP/IP 的五层协议的关系
这里有个疑问, TCP/IP 不是五层协议吗, 怎么变成四层了, 你可以就这么理解, 只是五层协议将 TCP/IP 协议的网络接口层, 更加的颗粒度了, 将其分割成物理层和数据链路层了, 本质是一样的。
OSI 七层协议和 TCP/IP 的五层协议是两种常用的网络协议参考模型,它们描述了计算机网络中不同功能层次的划分及其相互关系。以下是两者的关系及对比:
OSI 七层协议
OSI(Open Systems Interconnection)模型是一种理论框架,定义了七个层次:
- 物理层(Physical Layer):负责数据的物理传输(如电缆、光纤、无线电信号)。
- 数据链路层(Data Link Layer):负责节点间的数据帧传输和错误检测。
- 网络层(Network Layer):负责数据包的路由和转发(如 IP)。
- 传输层(Transport Layer):负责端到端的数据传输和流量控制(如 TCP、UDP)。
- 会话层(Session Layer):负责建立、管理和终止会话(如会话管理、同步)。
- 表示层(Presentation Layer):负责数据格式转换和加密(如加密、压缩)。
- 应用层(Application Layer):为用户提供网络服务接口(如 HTTP、FTP)。
TCP/IP 五层协议
TCP/IP 模型更贴近实际使用,简化了 OSI 模型,分为五个层次:
- 物理层(Physical Layer):与 OSI 的物理层功能相同。
- 数据链路层(Data Link Layer):与 OSI 的数据链路层功能相同。
- 网络层(Network Layer):与 OSI 的网络层功能相同,主要协议是 IP。
- 传输层(Transport Layer):与 OSI 的传输层功能相同,主要协议是 TCP 和 UDP。
- 应用层(Application Layer):合并了 OSI 的会话层、表示层和应用层,直接提供用户应用服务(如 HTTP、SMTP)。
两者的关系
-
层级划分的相似性:
- TCP/IP 五层协议的物理层、数据链路层、网络层和传输层与 OSI 模型完全对应。
- TCP/IP 五层模型的应用层包含了 OSI 的会话层、表示层和应用层的功能。
-
理论 vs 实际:
- OSI 模型是一个理论参考模型,结构更细致,用于指导协议开发。
- TCP/IP 模型是实际网络中广泛使用的协议模型,强调实用性。
-
使用场景:
- OSI 模型更多用于教学和理论分析。
- TCP/IP 模型是现代网络实际使用的主要框架,Internet 的核心协议(如 IP 和 TCP)基于此模型。
经过上面的过程, 浏览器可以下载获取构建网页所需的 html, css , js 文件, 并将这些文件交给浏览器的渲染进程
解析引擎
这里我们需要分清楚浏览器内核和 js 引擎的关系
浏览器内核和JS引擎是浏览器的两个不同但相互依赖的组件。以下是二者的具体关系:
-
浏览器内核
- 定义:浏览器内核,也称为渲染引擎或排版引擎,是一种软件组件,负责获取网页内容(如HTML、XML等),整理信息(如加入CSS样式),并计算网页的显示方式,最终输出至显示器或打印机。
- 功能:它主要处理页面的结构、布局和渲染,确保网页内容能够正确显示。不同的浏览器内核对网页语法的解释可能有所不同,这会导致相同的网页在不同浏览器中显示效果有所差异。
- 常见内核:常见的浏览器内核包括Trident(用于IE)、Gecko(用于Firefox)、WebKit(用于Safari和早期的Chrome)以及Blink(用于现在的Chrome和Opera)。
-
JS引擎
- 定义:JS引擎是一个专门处理JavaScript脚本的虚拟机,通常会附带在网页浏览器中。它负责解析和执行JavaScript代码,提供了JavaScript运行的环境。
- 功能:JS引擎将JavaScript代码编译成机器码,以便CPU执行。它还负责内存管理、垃圾回收等任务。一个高效的JS引擎可以显著提升网页的响应速度和用户体验。
- 常见引擎:常见的JS引擎包括V8(用于Chrome和Node.js)、SpiderMonkey(用于Firefox)、JavaScriptCore(用于Safari)和Chakra(用于Edge)。
-
浏览器内核与JS引擎的关系
- 相辅相成:浏览器内核和JS引擎共同工作,使得网页能够正常显示并与用户交互。内核负责页面的结构和样式,而JS引擎负责动态内容的执行和交互。
- 独立发展:虽然早期浏览器内核和JS引擎没有明确区分,但随着技术的发展,JS引擎变得越来越独立,可以单独进行测试和优化。这种独立性使得开发者可以更专注于提升JavaScript的执行效率。
解析文件
浏览器将下载的 html、css、js 文件交给渲染进程, 渲染进程会开启一个渲染主线程去解析这些文件,主要流程如下图
我们可以看出 js 文件的下载和执行会阻塞主流程, 在 script 中有两个参数可以处理这两种情况
感兴趣的可以自己下去了解, 这里只介绍主流程, html 和 css 文件会给渲染引擎去渲染, js 文件会给 js 引擎解析, 这里介绍 V8 引擎。
html 和 css 解析
关键渲染路径
-
解析
从图中我们可以得知浏览器渲染引擎会对 html 文件进行解析并生成 DOM 树, 对 CSS 进行解析并生成 CSSOM 树
当浏览器拿到 html 字符传的时候, 会再开启一个线程--- 预解析线程, 因为下载解析 css 文件也需要时间, 如果全放在主线程会有时间忧虑, 为了提高效率,所以会开启预解析线程帮助主线程进行css的预下载和解析,解析完成后返回给渲染主线程, 让主线程去生成 CSSOM 树, 所以 css 的解析不会阻塞 html
生成的 DOM 树结构:
最后生成的 CSSOM 树结构:
可以通过 document.styleSheets 获取
-
样式计算
解析完成后会获取到 DOM 树和 CSSOM 树, 这一步会对 DOM 树中的节点信息进行计算, 这一过程中很多值都会变成绝对值, 例如: red 会变成 rgba , em 会变成 px , 这一步完成后, 会获取到一颗样式计算树。
样式标准化过程:
计算完成后的样式计算树, 他会继承父类的一些属性, 如图中灰色部分, 也会覆盖继承的属性, 最终我们得到的一个颗样式计算树, 但这并不是最终的渲染树
- 布局
在上一步, 我们拿到了最终的计算样式树, 在这一步,我们会根据这些包含几何信息的树状结构进行布局,那些不包含几何信息的盒子不会出现在布局树中, 比如 display: none; , 除此之外根据 W3C 的规定, 内容必须在行盒中, 行盒和块盒不能相邻 这导致他会生成一些匿名块盒和匿名行盒
计算渲染树中每个元素的大小和位置。具体来说,浏览器会从顶部开始一直向下遍历渲染树,计算应显示每个节点的坐标
- 分层
浏览器的分层模式在某些老版本的浏览器没有,建议更新谷歌到最新浏览器寻找分层的设计理念就是为了防止用户频繁更新页面而设计出来的,用户更改这一层的数据只 会对这一层进行更新从而提升效率。滚动条、堆叠上下文、transform、opacity 等, 提高了浏览器的性能
- 绘制
渲染引擎就会对图层树上的每个图层进行绘制, 会把每个图层分成很多小的绘制指令, 我们可以在浏览器中查看这些绘制指令集
直到上面渲染主线程的工作就完成了, 他会将生成的绘制指令集列表交给合成线程, 最终的绘制是由合成线程完成的, 他们的关系如下图
- 分块
为了提高效率合成线程会开启一个线程池,对这些绘制指令进行分块,会将每一层分成多个小的区域, 这些线程池处理完成后再返回给合成主线程
- 光栅化
交给GPU进程来执行光栅化,最后生成位图,光栅化会将每个块变成位图,优先处理靠近视口的块 合成线程会将生成绘制命令 Draw Quad 交给浏览器进程 (通知GUP进程,GPU 进程 通知硬件画出图像),通过这个通知操作系统的 GPU 进程, 以极高的速度完成光栅化。 GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。 光栅化的结果,就是一块一块的位图
- 画
合成线程计算出每个位图在屏幕上的位置,最终交给GPU呈现, 形成我们看到的网页信息
对于浏览器的渲染补充 3 个知识点
-
默认值
在写 html 结构的时候为什么 div, p 这些就是块盒? 因为浏览器在实现的时候在代码中就规定好了, 在浏览器中所有的样式都通过 css 控制,与标签无关
-
回流
改变了元素的几何信息, 导致计算样式树需要重新进行计算, 所以尽量不要使用 js 去操作元素的几何信息, 这对性能有较大的影响, 每次回流都会经历后面的步骤
-
重绘
改变了元素的改动了可见样式信息, 比如颜色之类的, 他影响路径上的绘制阶段; 本质就是重新根据分层信息计算了绘制指令(Paint)
重绘不会引起回流, 但是回流一定会引起重绘
js 解析
什么是JavaScript引擎
我们编写的 JavaScrip t代码,它实际上是一种高级语言,这种语言并不是机器语言。
- 高级语言是设计给开发人员使用的,它包括了更多的抽象和可读性。但是,计算机的 CPU 只能理解特定的机器语言,它不理解 JavaScript 语言。这意味着,在计算机上执行 JavaScript 代码之前,必须将其转换为机器语言。
这就是 JavaScript 引擎的作用(将高级语言转换成 CPU 能够识别的机器语言):
- 事实上我们编写的 JavaScript 无论交给浏览器还是 Node 执行,最后都是需要被 CPU 执行的;但是CPU 只认识自己的指令集,实际上是机器语言,才能被 CPU 所执行;所以我们需要 JavaScript 引擎帮助我们将 JavaScript 代码翻译成 CPU 指令来执行;
常见的 Javascript 引擎
SpiderMonkey:第一款JavaScript引擎,由Brendan Eich开发(也就是JavaScript作者);Chakra:微软开发,用于IE浏览器;JavaScriptCore:WebKit中的JavaScript引擎,Apple公司开发;V8:Google开发的强大JavaScript引擎,也帮助Chrome从众多浏览器中脱颖而出;
他与浏览器内核间的关系如图
浏览器内核负责整体的页面布局和资源加载,而 JavaScript 引擎专注于处理 JavaScript 语言,以 Chrome 为例,当 JavaScript 的文件被下载完成后, 会交给 V8 引擎去解析。
V8引擎的原理
看一下官网对其的定义, v8.dev/docs
V8 实现了 ECMAScript 和 WebAssembly,并在使用 x64、IA-32 或 ARM 处理器的 Windows、macOS 和 Linux 系统上运行。其他系统(IBM i、AIX)和处理器(MIPS、ppcle64、s390x)由外部维护,参见 “端口”。V8 可以嵌入到任何 C++ 应用程序中。
V8 编译并执行 JavaScript 源代码,处理对象的内存分配,并对不再需要的对象进行垃圾回收。V8 的 “停止世界”、分代、准确的垃圾回收器是 V8 性能的关键之一。
JavaScript 通常用于浏览器中的客户端脚本,例如用于操作文档对象模型(DOM)对象。然而,DOM 通常不是由 JavaScript 引擎提供,而是由浏览器提供。V8 也是如此 —— 谷歌 Chrome 提供 DOM。但是,V8 确实提供了 ECMA 标准中指定的所有数据类型、运算符、对象和函数。
可以大致看一下 v8 是怎么处理 js 的
-
解析
将 js 代码解析生成一种特殊的结构 AST 抽象语法树, 会经过两个阶段, 预解析和全量解析
-
生成字节码
将生成好的 AST 交给解释器 Ignition 去解释成字节码, 字节码是基于源码和机器语言之间的一种低级语言
-
优化
TurboFan 函数对运行中的字节码进行优化, 生成优化后的机器码, 可以提高整体的性能
-
去优化
如果在执行过程中,发现他不满足原来的设定的优化策略, 会将原来优化的机器码重新转换成字节码
-
生成机器码
机器码是一种可以直接被运行的代码, 运行速度很快, 被优化后生成的机器码可以直接被运行
这就是 V8 处理 javascript 的整个流程, 对于一些关键流程我们需要展开来说, 主要有 3 个内容, parse、ignition、turbofan
- parse(解析器)
官网地址: v8.dev/blog/scanne…
会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码;
-
函数不管有没有调用都会被转化为AST,因为即使函数没有被立即调用,它们可能在将来被用作回调或传递给其他函数。此外,函数内部可能包含需要提前处理的声明或表达式。所以解析器不能仅仅因为函数当前未被调用就忽略它们,必须分析所有代码以建立完整的程序结构
-
ignition (解释器)
官网地址: v8.dev/docs/igniti…
会将 AST 转换成字节码, 同时收集 Turbofan 优化所需的信息
- turbofan
官网地址: v8.dev/blog/turbof…
可以将字节代码直接编译成计算机能够识别的机器码, 如果一个函数被多次调用, 就会被标记成一个热点函数, 那么就会经过 Turbofan 转换成优化的机器, 提高效率。
- 解析过程
解析阶段是一个关键的阶段, 看一下官方的解释图
Blink: 是流程的开始阶段, js 代码以不同的编码被读入, 最终转换成数据流(chunk) Stream: 接受来自 Blink 传入的数据流并将其转换成 UTF-16 编码格式,准备进行下一步的扫描 Scanner: 将流式传入的代码进行扫描, 生成 Token, 这些都是生成 AST 的基本元素 PreParser:对 tokens 进行扫描, 确定他的基本结构 Parser: 全量解析, 解析tokens,构建AST。AST是源代码的树形结构的详细表现形式,用于指导后续的编译和优化 ByteCode: 将 AST 生成字节码, 能被 V8 快速执行,以及被优化
-
词法分析
将代码解析成一行行词法单元(Token),对应 Scanner 的时候, 解析的时候就是将一个个词法单元拼接成我们想要的 AST, 会将代码编写成一个个这样的词法单元
Token(type='Keyword', value='const') // 声明一个常量
Token(type='Identifier', value='name') // 变量名
Token(type='Operator', value='=') // 赋值操作符
Token(type='StringLiteral', value='"coderwhy and XiaoYu"') // 字符串常量
Token(type='Punctuation', value=';') // 语句结束符
Token(type='Identifier', value='console') // 对象名
Token(type='Punctuation', value='.') // 成员访问操作符
Token(type='Identifier', value='log') // 方法名
Token(type='Punctuation', value='(') // 函数调用开始括号
Token(type='Identifier', value='name') // 参数名
Token(type='Punctuation', value=')') // 函数调用结束括号
Token(type='Punctuation', value=';') // 语句结束符
Token(type='Keyword', value='function') // 函数关键字
Token(type='Identifier', value='sayHi') // 函数名
Token(type='Punctuation', value='(') // 参数列表开始括号
Token(type='Identifier', value='name') // 参数名
Token(type='Punctuation', value=')') // 参数列表结束括号
Token(type='Punctuation', value='{') // 函数体开始括号
Token(type='Identifier', value='console') // 对象名
Token(type='Punctuation', value='.') // 成员访问操作符
Token(type='Identifier', value='log') // 方法名
Token(type='Punctuation', value='(') // 函数调用开始括号
Token(type='StringLiteral', value='"Hi "') // 字符串常量
Token(type='Operator', value='+') // 字符串连接操作符
Token(type='Identifier', value='name') // 参数名
Token(type='Punctuation', value=')') // 函数调用结束括号
Token(type='Punctuation', value=';') // 语句结束符
Token(type='Punctuation', value='}') // 函数体结束括号
Token(type='Identifier', value='sayHi') // 函数名
Token(type='Punctuation', value='(') // 函数调用开始括号
Token(type='Identifier', value='name') // 参数名
Token(type='Punctuation', value=')') // 函数调用结束括号
Token(type='Punctuation', value=';') // 语句结束符
type属性帮助解析器(parser)识别每个token的角色和功能,从而进行进一步的语法分析和代码执行。我们总结其中一部分来进行参考,每个类型所对应的含义都是一部分小知识点,在JS基础中,我们都有学过,在这基础上,我们可以理解type的作用以及存在的必要
- Keyword: 关键字,如
const,function等。是JavaScript语法的核心部分,指示解析器代码的结构和行为 - Identifier: 标识符,通常用于变量名、函数名等。是程序员定义的符号,用于表示变量存储位置或函数名称
- Operator: 操作符,如
=,+等。操作符定义了一些操作,赋值、算术计算等,它们对数据执行特定的运算 - StringLiteral: 字符串字面量,表示源代码中直接出现的字符串值
- Punctuation: 标点符号,如逗号(
,)、分号(;)、圆括号((,))和大括号({,})。这些符号用于分隔代码中的各个部分,帮助解析器确定语句的开始和结束,以及组织代码块,
除此之外, 他还会记录每个标识符号的位置等信息, 这就是为什么我们在 const 声明的参数之前调用会知道有问题并出现暂时性死区, 因为他的位置信息都被记录了。
-
语法分析
语法分析,是指解析阶段将 Tokens 转换成 AST 语法树的过程, 他分为预解析和全量解析的过程,但解析前会有一个预处理阶段, 这个阶段会扫描整个代码,识别和处理那些能够独立于完整代码逻辑进行解析的结构(变量、函数声明...)
-
预解析
主要目的是快速扫描代码以提取基本的结构信息,如变量和函数声明。这种解析方式不会深入分析函 数体内的具体逻辑或表达式,因此速度相对较快,会生成 AST 语法树
-
全量解析
在预解析的基础上,它会详细分析整个代码文件,包括函数体内的所有语句和表达式。这种解析方式生成的 AST 更加完整,包含了代码中的所有细节
-
我们可以在这个网站查看 AST 语法树生成的结构:astexplorer.net/
最后将生成的 AST 转换成字节码, 或者被优化后生成机器码交给 CPU 去处理
到这里浏览器处理 html、css、js 文件的主要流程就介绍完了
四次挥手
在下载完资源后需要和服务器断开连接, 其实就是 TCP/IP 断开连接, 客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
四次挥手示意图:
-
第一次挥手
客户端打算关闭连接,会发送一个 TCP 首部
FIN标志位置被设置为 1 的报文, 之后客户端进入FIN_WAIT_1状态。 -
第二次挥手
服务端收到该报文后,向客户端发送
ACK应答报文,接着服务端进入CLOSED_WAIT状态 -
第三次挥手
客户端收到服务端的
ACK应答报文后,之后进入FIN_WAIT_2状态。等待服务端处理完数据后,也向客户端发送FIN报文,之后服务端进入LAST_ACK状态。 -
第四次挥手
- 客户端收到服务端发来的
FIN后会发送一个ACK给服务端, 之后进入TIME_WAIT - 服务端接收到客户端发来的
ACK后, 客户端进入CLOSE, 至此服务端完全关闭 - 客户端在经过
2MSL时间后, 自动CLOSE, 至此客户端完全关闭
- 客户端收到服务端发来的
以上就是浏览器输入一个网址后背后的主要逻辑