简单版本的,可以看这个juejin.cn/post/725366...
知识背景
浏览器的结构
- 用户界面(User Interface) 包括地址栏、后退/前进按钮、书签目录等,也就是看到的除了显示所请求页面的主窗口之外的其他部分
- 浏览器引擎(Browser Engine) 在用户界面和渲染引擎之间传送指令。
- 渲染引擎 (Rendering Engine) 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
- 网络(Networking) 用于网络调用,比如 HTTP 请求。
- JS解释器(JavaScript Interpreter) 用于解析和执行 JavaScript 代码。
- 用户界面后端(UI Backend) 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
- 数据持久层 (Data Persistence) 这是持久层。浏览器需要在硬盘上保存各种数据,例如书签、cookie和偏好设置等。
常见的不同浏览器的内核
进程与线程
- 进程:操作系统进行资源分配和调度的基本单元,可以申请和拥有计算机资源,进程是程序的基本执行实体。
- 线程:操作系统能够进行运算和调度的最小单位,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
当我们启动某个程序时,就会创建一个进程来执行任务代码,同时会为该进程分配内存空间,该应用程序的状态都保存在该内存空间中,应用关闭时,内存空间会被回收
- 进程之间分配的内存空间是独立的,两个进程之间需要传递数据,需要通过进程间通信管道IPC来传递。
- 很多应用程序都是多进程的结构,这样可以避免某一个进程卡死。进程间相互独立,就不会影响到整个应用程序。
- 比如鼠标就是单独的进程,如果卡死了也不会影响到电脑的运行
- 进程可以将任务分成更细小的任务,通过创建多个线程并行执行不同的任务。同一进程之间的线程之间是可以直接通信共享数据的。
进程与线程的区别总结
-
本质区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。
-
包含关系:一个进程至少有一个线程,线程是进程的一部分
-
资源开销:每个进程都有独立的地址空间,进程之间的切换会有较大的开销;线程可以看做轻量级的进程,同一个进程内的线程共享进程的地址空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
-
影响关系:一个进程崩溃后,在保护模式下其他进程不会被影响,但是一个线程崩溃可能导致整个进程被操作系统杀掉,所以多进程要比多线程健壮。
早期浏览器是单进程
单进程的缺点:
1.不稳定:一个线程卡死影响整个浏览器
2.不安全:线程之间数据共享,js线程可以随意访问浏览器内的所有数据。
3.流畅:进程需要负责事务过多
现在的多进程浏览器
1.浏览器进程负责控制chrome浏览器除标签页外的用户界面(见下图),以及负责与浏览器的其他进程的协调工作
2.GPU进程负责整个浏览器界面的渲染
3.插件进程负责控制网站使用的所有插件,如flash
4.渲染器进程用来控制显示tab标签内的所有内容,浏览器在默认情况下会为每个标签页都创建一个进程
5.网络进程负责接收和发送网络请求
6.浏览器自动保存网页的部分或全部资源文件(例如图片、JavaScript、CSS、HTML等),以便在下一次访问同样的网页时,这些文件可以更快地被找到和加载。这样做可以减少浏览器向服务器发出请求的次数,从而提升用户访问网站的速度和效率。
从输入url到页面加载完成中间发生了什么呢?
大概流程
- 1、输入URL
- 2、浏览器缓存查找当前URL
- 3、DNS解析URL
- 4、建立TCP连接(三次握手)
- 5、HTTP发起请求
- 6、服务器处理请求,浏览器接收HTTP响应
- 7、关闭TCP连接(四次挥手)
- 8、渲染页面,构建DOM树
1、输入URL
URL(Uniform Resource Locator)是用于定位和标识互联网资源的地址。它由多个组成部分组成,用于指定资源的位置和访问方式。下面是URL的详细解释和各个组成部分的说明:
eg:http://www.example.com:80/path/to/resource?query=string#fragment
-
协议(Protocol):在URL的开头部分,用于指定访问资源所使用的协议。常见的协议包括:
- HTTP(超文本传输协议):用于在Web浏览器和Web服务器之间传输数据。
- HTTPS(安全超文本传输协议):通过SSL或TLS协议对HTTP进行加密,提供安全的数据传输。
- FTP(文件传输协议):用于在客户端和服务器之间传输文件。
- SMTP(简单邮件传输协议):用于发送电子邮件。
- 等等。
-
主机名(Host):主机名指示要访问的服务器的域名或IP地址。在示例中,主机名是"www.example.com"
-
端口(Port):可选项,指定要连接到的服务器上的端口号。如果未指定,默认使用协议的默认端口号。在示例中,端口是":80",表示使用HTTP协议的默认端口。
-
路径(Path):指定服务器上资源的路径或文件名。在示例中,路径是"/path/to/resource"。
-
查询字符串(Query String):可选项,用于向服务器传递参数或数据。它以"?"符号开始,参数之间使用"&"分隔。在示例中,查询字符串是"?query=string"。
-
片段标识(Fragment):可选项,指定文档中的特定片段或锚点。它以"#"符号开始。在示例中,片段标识是"#fragment"。
2、浏览器缓存查找当前URL
浏览器缓存是指浏览器在本地存储已经访问过的资源副本,以便在后续的请求中可以更快地获取这些资源。通过使用缓存,浏览器可以避免重新下载相同的资源,从而提高页面加载速度,减少网络流量和服务器负载。
浏览器缓存可以分为两种类型:强缓存和协商缓存。
- 强缓存:强缓存是通过在HTTP响应头中设置一些参数来控制的,使得浏览器可以在本地缓存中直接获取资源,而不需要向服务器发送请求。常见的强缓存相关的响应头字段包括:
Cache-Control:该字段用于指定缓存策略。常见的取值包括:
diff
- public:表示响应可以被任何缓存(包括客户端和代理服务器)缓存。
- private:表示响应只能被客户端缓存,而不能被代理服务器缓存。
- no-cache:表示缓存服务器不能直接使用缓存的响应,而是需要先向源服务器验证该资源是否仍然有效。
- max-age=<seconds>:表示缓存的有效期,即资源在缓存中保存的时间(以秒为单位)。
- s-maxage=<seconds>:类似于max-age,但仅适用于共享缓存(如代理服务器)。
Expires:该字段指定了一个绝对的过期时间,表示资源在此时间之后被认为是过期的。值是一个HTTP日期格式的字符串。
-
协商缓存:协商缓存是通过与服务器进行通信来确定是否可以使用缓存的资源副本。当浏览器发送请求时,服务器会返回一些响应头字段,用于标识资源的标识符和相关的缓存信息。如果浏览器在本地缓存中有相应的资源副本,它会与服务器提供的信息进行比较,以确定是否可以使用缓存的副本。常见的协商缓存相关的响应头字段包括:
- Last-Modified:指示资源的最后修改时间。
- If-Modified-Since:当浏览器再次请求资源时,会将上次的Last-Modified值通过该字段发送给服务器,服务器可以根据该值判断资源是否有变化。
- ETag:指示资源的唯一标识符,可以是资源内容的哈希值等。
- If-None-Match:当浏览器再次请求资源时,会将上次的ETag值通过该字段发送给服务器,服务器可以根据该值判断资源是否有变化。
当浏览器判断可以使用缓存时,它会直接从本地缓存中获取资源,并且不会发送请求到服务器。如果缓存失效或需要重新验证资源是否过期,浏览器会发送请求到服务器,并根据服务器的响应决定是使用缓存还是获取新的资源副本。
浏览器缓存对于提高网页加载速度和降低服务器负载非常重要。但有时候,如果服务器上的资源发生了更新,可能需要手动刷新缓存,以获取最新的资源。在开发过程中,可以通过设置响应头字段来控制缓存的行为,以便进行调试和测试。
3、DNS解析URL
- 请求发起后,游览器首先会解析这个域名,首先它会查看本地硬盘的 hosts 文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用 hosts 文件里面的 ip 地址。
- 本地DNS缓存查询:首先会检查本地DNS缓存中是否存在该域名的解析结果。如果存在,将直接返回对应的IP地址,查询结束。
- 递归查询:如果本地DNS缓存中没有域名的解析结果,本地DNS服务器将发起递归查询。它向根DNS服务器发送一个请求,询问根服务器所知道的顶级域名服务器(如.com、.net等)的地址。
- 根DNS服务器查询:根DNS服务器收到查询请求后,会根据顶级域名(如.com)提供本地顶级域名服务器的地址给本地DNS服务器。
- 顶级域名服务器查询:本地DNS服务器使用从根DNS服务器获得的顶级域名服务器地址,向顶级域名服务器发送查询请求。顶级域名服务器根据查询请求中的二级域名(如example.com)提供权威域名服务器的地址。
- 权威域名服务器查询:本地DNS服务器使用从顶级域名服务器获得的权威域名服务器地址,向权威域名服务器发送查询请求。权威域名服务器包含了所查询域名的DNS记录。
- DNS记录返回:权威域名服务器收到查询请求后,会查找并返回所需域名的DNS记录,包括IP地址或其他记录类型(如MX记录、CNAME记录等)。
- 结果返回给本地DNS服务器:权威域名服务器将查询结果返回给本地DNS服务器。
- 结果返回给用户:本地DNS服务器接收到查询结果后,将结果存储到本地DNS缓存中,并将查询结果返回给用户的设备。
- 用户访问目标网站:用户设备接收到DNS解析结果(IP地址),将使用该IP地址与目标网站建立连接,并发起访问请求。
4、建立TCP连接(三次握手)
TCP三次握手的过程如下:
- 第一次握手(SYN): 客户端(通常是浏览器)向服务器发送一个特殊的TCP报文段,其中设置了SYN(同步)标志位,并选择一个随机的初始序列号(ISN)作为初始通信序列号。这个报文段表示客户端请求建立连接。
- 第二次握手(SYN-ACK): 服务器接收到客户端的SYN报文段后,会发送一个响应报文段作为回应。该响应报文段中设置了SYN和ACK(确认)标志位,并将确认号(ACK)设置为客户端的初始序列号加一,同时服务器也选择了一个随机的初始序列号(ISN)。这个报文段表示服务器接受了客户端的请求,并告知客户端可以开始发送数据。
- 第三次握手(ACK): 客户端接收到服务器的SYN-ACK报文段后,会发送一个带有ACK标志位的报文段作为确认。客户端将确认号设置为服务器的初始序列号加一,同时将自己的序列号设置为初始序列号加一。这个报文段表示客户端接受了服务器的响应,并告知服务器可以开始发送数据
5、HTTP发起请求
浏览器构建一个HTTP请求消息,包括请求行、请求头和请求体。请求行包含请求方法(GET、POST、PUT等)和请求的URL。请求头包含关于请求的附加信息,例如Accept(指示浏览器可以接受的内容类型)、Cookies(存储在浏览器中的服务器相关数据)等。请求体(在某些请求中存在)包含要发送给服务器的数据,例如在POST请求
6、服务器处理请求,浏览器接收HTTP响应
响应报文主要包括以下几个方面:
- 状态行(Status Line):响应报文的第一行是状态行,用于指示服务器对请求的处理结果。状态行由三部分组成:HTTP协议版本号、状态码和状态消息。例如,一个常见的状态行可能是:HTTP/1.1 200 OK。其中,HTTP/1.1是协议版本号,200是状态码表示成功,OK是状态消息。
- 响应头(Response Headers):紧接着状态行后面是一系列的响应头。响应头提供了关于响应的附加信息,例如服务器类型、日期、内容类型等。每个响应头都由一个名称和一个值组成,中间用冒号分隔。例如,Content-Type: text/html表示响应的内容类型是HTML文档。 3 响应体(Response Body):空行之后是响应体,它包含了服务器返回给客户端的实际数据。响应体的内容格式取决于响应的内容类型。例如,如果内容类型是text/html,则响应体可能包含HTML标记的文本。
状态码分类 | 说明 |
---|---|
1XX | 响应中--临时状态码,表示请求已经接受,告诉客户端应该继续请求或者如果它已经完成则忽略它 |
2XX | 成功--表示请求已经被成功接受,处理已完成 |
3XX | 重定向--重定向到其他地方(让客户端再发起一个请求以完成整个处理) |
4XX | 客户端错误--处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等 |
5XX | 服务器端错误--处理发生错误,责任在服务器端,如:服务端抛出异常,路由出错,HTTP版本不支持等 |
7、关闭TCP连接(四次挥手)
在TCP协议中,当通信双方完成数据交换或需要断开连接时,可以选择关闭TCP连接或继续保持连接。
- 关闭TCP连接:如果通信双方都完成了数据交换,并且不再需要保持连接,则可以通过四次握手的过程来关闭TCP连接。四次握手是指通过发送特定的TCP报文段来协商连接的关闭,确保双方都知道连接即将关闭。一旦完成四次握手,TCP连接就会被正式关闭,释放相关的资源。
- 继续保持连接:在某些情况下,通信双方可能需要保持连接的状态,以便在将来的时间内继续进行数据交换。这种情况下,双方可以选择不关闭TCP连接,而是保持连接处于活动状态。通过保持连接,可以减少建立新连接的开销,并在需要时立即进行通信。
选择关闭TCP连接还是继续保持连接取决于具体的应用场景和需求。在某些情况下,例如网页浏览,每个HTTP请求都可以使用独立的TCP连接,因此可以在每个请求之后关闭连接。而在其他情况下,例如实时通信应用程序,可能需要保持长时间的连接,以便实时传输数据。
四次挥手:
第一次挥手: Client端发起挥手请求,向Server端发送标志位是FIN报文段,设置序列号seq,此时,Client端进入FIN_WAIT_1状态,这表示Client端没有数据要发送给Server端了。
第二次挥手:Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK的报文段,Server端告诉Client端,我收到了你的关闭请求。
第三次挥手: Server端向Client端发送标志位是FIN的报文段,请求关闭连接,同时Client端进入LAST_ACK状态。
第四次挥手 : Client端收到Server端发送的FIN报文段,向Server端发送标志位是ACK的报文段,然后Client端进入TIME_WAIT状态。Server端收到Client端的ACK报文段以后,就关闭连接。此时,Client端等待2MSL(最长报文段寿命)的时间后依然没有收到Server的重发FIN报文,则证明Server端已正常关闭,那好,Client端也可以关闭连接了。
8、渲染页面,构建DOM树
- 解析HTML:浏览器首先接收到HTML代码,并进行解析。解析过程将HTML代码转换为一个由多个节点组成的树状结构,即DOM树(Document Object Model)。解析器会逐个读取HTML标记和文本内容,并根据标记的嵌套关系构建DOM节点。
- 构建DOM树:解析器根据HTML标记的嵌套关系和文本内容构建DOM树。每个HTML元素都表示为DOM树中的一个节点,包括根节点(通常是
<html>
元素)、父节点、子节点和兄弟节点等。 - 处理CSS:在构建DOM树的同时,浏览器还会处理CSS样式信息。浏览器会解析CSS样式表,并将样式规则应用于DOM节点,计算出每个节点的最终样式。这包括计算元素的尺寸、颜色、字体等属性。
- 构建渲染树:浏览器将DOM树和计算好的CSS样式信息组合成渲染树(Render Tree)。渲染树只包含需要显示的节点,并且每个节点都与其对应的样式信息关联。渲染树的构建过程中,一些不显示的元素(如
<head>
、<script>
等)会被省略。 - 布局(回流):渲染树构建完成后,浏览器进行布局(也称为回流)操作。布局过程确定每个渲染树节点在页面中的确切位置和大小。浏览器会计算每个元素在屏幕上的几何位置,并考虑到盒模型、浮动、定位等因素。
- 绘制(重绘):布局完成后,浏览器进入绘制(也称为重绘)阶段。在绘制过程中,浏览器根据渲染树的布局信息将页面元素绘制到屏幕上。这包括绘制元素的边框、背景、文本等。
- 显示:绘制完成后,浏览器会将绘制好的页面内容显示在用户的屏幕上。
接下来先看一看整体的过程
渲染页面-构建DOM树
- HTML首先经过tokeniser标记化,通过词法分析将输入的html内容,解析成多个标记,根据识别后的标记进行DOM树构造,如下图HTML解析成一个一个标记
- 紧接着把标记生成相对应的dom树,可以看做左右结构,token栈和dom树。
- 当闭合的标记有了后,就会从栈中移除,树中也不会继续在 h1 下添加树节点,然后同理继续解析下一个标记
如何处理 script 标签?
- 解析过程中遇到script标签,会暂停解析,转而去加载解析并且执行js
为什么 JS 阻塞页面加载 ?
- 由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,GUI 渲染线程与 JavaScript 引擎为互斥的关系。
异步加载 script
async script
当浏览器遇到带有 async 属性的 script 时,请求该脚本的网络请求是异步的,不会阻塞浏览器解析 HTML,一旦网络请求回来之后,如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再进行解析
defer script
当浏览器遇到带有 defer 属性的 script 时,获取该脚本的网络请求也是异步的,不会阻塞浏览器解析 HTML,一旦网络请求回来之后,如果此时 HTML 还没有解析完,浏览器不会暂停解析并执行 JS 代码,而是等待 HTML 解析完毕再执行 JS 代码
异步加载 script总结
style样式计算
- 样式解析也会生成cssom的树。
有了dom、cssom后,我们要把dom和cssom结合成布局树。并且接下来需要知道每个节点需要放在页面上的哪个位置,以及需要占有页面多大的区域,这个阶段被称为layout布局。
layout布局
通过遍历方式选中所有可见的节点,把节点添加至布局树中
注意:dom tree和layout tree不是一一对应的
设置了display:none的节点不会出现在layout tree上。在before伪类中添加了content值的元素content里的内容会出现在layout tree上,而不会出现在DOM树里
分层
- 分层是为了实现层级操作,如css的z-index属性
- 遍历layout生成paint 接下来还需要知道以什么样的顺序绘制节点,主线程遍历layout tree 创建一个绘制记录表(paint record),该表记录了绘制的顺序
绘制
绘制列表将绘制记录表给到合成线程
现在的chrome使用一种叫做"合成"的栅格化流程
1.合成器线程将每个图层栅格化,由于一层可能像整个页面长度那么大,因此合成器线程将他们切分为许多图块,然后发送给栅格线程。
2.栅格线程将每个图块栅格化,并将其存储在GPU内存中,这些信息里记录了图块在内存中的位置,和在页面的哪个位置绘制图块的信息。
3.根据这些信息,合成器线程生成一个合成器帧,然后合成器帧通过IPC传送给浏览器进程
4.浏览器进程将合成器帧传送到GPU,然后,GPU渲染展示到屏幕上。
- 页面发生变化后,会生成新的合成器帧,新的帧在传给GPU,然后再次渲染到屏幕上
自此,看到东西了。撒花撒花~
重排
改变元素的尺寸位置属性时,会重新进行样式计算(computed style)、布局(layout)、绘制(paint)以及后面的所有流程。这就是重排。
重绘
改变元素的颜色属性时,不会触发布局,但会触发样式计算和绘制。这就是重绘。
真难写呀,整理了不少时间。 哈哈哈~ 我也是翻阅了别人的一些资料和视频,整理出来的,如果有不对的地点,请不要吝啬,为我指出不足,谢谢大家!