【浏览器工作原理与实践笔记一】:宏观视角上的浏览器

【浏览器工作原理与实践笔记一】:宏观视角上的浏览器

文章目录

一、Chrome架构:仅仅打开了1个页面,为什么有4个进程

这一部分的笔记放在了别的博客中:点击这里👉



二、TCP协议:如何保证页面文件能被完整送达浏览器

在衡量Web⻚⾯性能的时候有⼀个重要的指标叫“FP(First Paint)”,是指从⻚⾯加载到⾸次开始绘制的时⻓。这个指标直接影响了⽤⼾的跳出率,更快的⻚⾯响应意味着更多的PV、更⾼的参与度,以及更⾼的转化率。那什么影响FP指标呢?其中⼀个重要的因素是⽹络加载速度

在⽹络中,⼀个⽂件通常会被拆分为很多数据包来进⾏传输,⽽数据包在传输过程中⼜有很⼤概率丢失或者出错。那么如何保证⻚⾯⽂件能被完整地送达浏览器呢


⼀个数据包的“旅程”

互联⽹,实际上是⼀套理念和协议组成的体系架构。其中,协议是⼀套众所周知的规则和标准,如果各⽅都同意使⽤,那么它们之间的通信将变得毫⽆障碍。

互联⽹中的数据是通过数据包来传输的。如果发送的数据很⼤,那么该数据就会被拆分为很多⼩数据包来传输。⽐如你现在听的⾳频数据,是拆分成⼀个个⼩的数据包来传输的,并不是⼀个⼤的⽂件⼀次传输过来的。

1. IP:把数据包送达目的主机

数据包要在互联⽹上进⾏传输,就要符合⽹际协议(Internet Protocol,简称IP)标准。互联⽹上不同的在线设备都有唯⼀的地址,地址只是⼀个数字,这和⼤部分家庭收件地址类似,你只需要知道⼀个家庭的具体地址,就可以往这个地址发送包裹,这样物流系统就能把物品送到⽬的地。

计算机的地址就称为IP地址,访问任何⽹站实际上只是你的计算机向另外⼀台计算机请求信息。

如果要想把⼀个数据包从主机A发送给主机B,那么在传输之前,数据包上会被附加上主机B的IP地址信息,这样在传输过程中才能正确寻址。额外地,数据包上还会附加上主机A本⾝的IP地址,有了这些信息主机B才可以回复信息给主机A。这些附加的信息会被装进⼀个叫IP头的数据结构⾥。IP头是IP数据包开头的信息,包含IP版本、源IP地址、⽬标IP地址、⽣存时间等信息。

为了⽅便理解,我先把⽹络简单分为三层结构,如下图:

在这里插入图片描述

下⾯我们⼀起来看下⼀个数据包从主机A到主机B的旅程:

  • 上层将含有“极客时间”的数据包交给⽹络层;

  • ⽹络层再将IP头附加到数据包上,组成新的 IP数据包,并交给底层;

  • 底层通过物理⽹络将数据包传输给主机B;

  • 数据包被传输到主机B的⽹络层,在这⾥主机B拆开数据包的IP头信息,并将拆开来的数据部分交给上层;

  • 最终,含有“极客时间”信息的数据包就到达了主机B的上层了。


2. UDP:把数据包送达应⽤程序

IP是⾮常底层的协议,只负责把数据包传送到对⽅电脑,但是对⽅电脑并不知道把数据包交给哪个程序,是交给浏览器还是交给王者荣耀?因此,需要基于IP之上开发能和应⽤打交道的协议,最常⻅的是“⽤⼾数据包协议(User Datagram Protocol)”,简称UDP。

UDP中⼀个最重要的信息是端⼝号,端⼝号其实就是⼀个数字,每个想访问⽹络的程序都需要绑定⼀个端⼝号。通过端⼝号UDP就能把指定的数据包发送给指定的程序了,所以IP通过IP地址信息把数据包发送给指定的电脑,⽽UDP通过端⼝号把数据包分发给正确的程序。和IP头⼀样,端⼝号会被装进UDP头⾥⾯,UDP头再和原始数据包合并组成新的UDP数据包。UDP头中除了⽬的端⼝,还有源端⼝号等信息。

为了⽀持UDP协议,我把前⾯的三层结构扩充为四层结构,在⽹络层和上层之间增加了传输层,如下图所示:

在这里插入图片描述

下⾯我们⼀起来看下⼀个数据包从主机A旅⾏到主机B的路线:

  • 上层将含有“极客时间”的数据包交给传输层;

  • 传输层会在数据包前⾯附加上UDP头,组成新的UDP数据包,再将新的UDP数据包交给⽹络层;

  • ⽹络层再将IP头附加到数据包上,组成新的IP数据包,并交给底层;

  • 数据包被传输到主机B的⽹络层,在这⾥主机B拆开IP头信息,并将拆开来的数据部分交给传输层;

  • 在传输层,数据包中的UDP头会被拆开,并根据UDP中所提供的端⼝号,把数据部分交给上层的应⽤程序;

  • 最终,含有“极客时间”信息的数据包就旅⾏到了主机B上层应⽤程序这⾥。

在使⽤UDP发送数据时,有各种因素会导致数据包出错,虽然UDP可以校验数据是否正确,但是对于错误的数据包,UDP并不提供重发机制,只是丢弃当前的包,⽽且UDP在发送之后也⽆法知道是否能达到⽬的地。

虽说UDP不能保证数据可靠性,但是传输速度却⾮常快,所以UDP会应⽤在⼀些关注速度、但不那么严格要求数据完整性的领域,如在线视频、互动游戏等。


3. TCP:把数据完整地送达应⽤程序

对于浏览器请求,或者邮件这类要求数据传输可靠性(reliability)的应⽤,如果使⽤UDP来传输会存在两个问题:

  • 数据包在传输过程中容易丢失;

  • ⼤⽂件会被拆分成很多⼩的数据包来传输,这些⼩的数据包会经过不同的路由,并在不同的时间到达接收端,⽽UDP协议并不知道如何组装这些数据包,从⽽把这些数据包还原成完整的⽂件。

基于这两个问题,我们引⼊TCP了。TCP(Transmission Control Protocol,传输控制协议)是⼀种⾯向连接的、可靠的、基于字节流的传输层通信协议。相对于UDP,TCP有下⾯两个特点:

  • 对于数据包丢失的情况,TCP提供重传机制;
  • TCP引⼊了数据包排序机制,⽤来保证把乱序的数据包组合成⼀个完整的⽂件。

和UDP头⼀样,TCP头除了包含了⽬标端⼝和本机端⼝号外,还提供了⽤于排序的序列号,以便接收端通过序号来重排数据包。

下⾯看看TCP下的单个数据包的传输流程:

在这里插入图片描述

TCP单个数据包的传输流程和UDP流程差不多,不同的地⽅在于,通过TCP头的信息保证了⼀块⼤的数据传输的完整性。

从下图可以看出,⼀个完整的TCP连接的⽣命周期包括了“建⽴连接”“传输数据”和“断开连接”三个阶
段。

在这里插入图片描述

  • ⾸先,建⽴连接阶段。这个阶段是通过“三次握⼿”来建⽴客⼾端和服务器之间的连接。TCP 提供⾯向连接的通信传输。⾯向连接是指在数据通信开始之前先做好两端之间的准备⼯作。所谓三次握⼿,是指在建⽴⼀个TCP连接时,客⼾端和服务器总共要发送三个数据包以确认连接的建⽴。

  • 其次,传输数据阶段。在该阶段,接收端需要对每个数据包进⾏确认操作,也就是接收端在接收到数据包之后,需要发送确认数据包给发送端。所以当发送端发送了⼀个数据包之后,在规定时间内没有接收到接收端反馈的确认消息,则判断为数据包丢失,并触发发送端的重发机制。同样,⼀个⼤的⽂件在传输过程中会被拆分成很多⼩的数据包,这些数据包到达接收端后,接收端会按照TCP头中的序号为其排序,从⽽保证组成完整的数据。

  • 最后,断开连接阶段。数据传输完毕之后,就要终⽌连接了,涉及到最后⼀个阶段“四次挥⼿”来保证双⽅都能断开连接。

TCP为了保证数据传输的可靠性,牺牲了数据包的传输速度,因为“三次握⼿”和“数据包校验机制”等把传输过程中的数据包的数量提⾼了⼀倍。

总结

  • 互联⽹中的数据是通过数据包来传输的,数据包在传输过程中容易丢失或出错。

  • IP负责把数据包送达⽬的主机。

  • UDP负责把数据包送达具体应⽤。

  • ⽽TCP保证了数据完整地传输,它的连接可分为三个阶段:建⽴连接、传输数据和断开连接。

你怎么理解HTTP和TCP的关系?

HTTP协议和TCP协议都是TCP/IP协议簇的⼦集。
HTTP协议属于应⽤层,TCP协议属于传输层,HTTP协议位于TCP协议的上层。
请求⽅要发送的数据包,在应⽤层加上HTTP头以后会交给传输层的TCP协议处理,应答⽅接收到的数据包,在传输层拆掉TCP头以后交给应⽤层的HTTP协议处理。建⽴ TCP 连接后会顺序收发数据,请求⽅和应答⽅都必须依据 HTTP 规范构建和解析HTTP报⽂。

tcp是个梯⼦,http就是利⽤梯⼦来搬运货物


三、HTTP请求流程:为什么很多站点第二次打开速度会很快

不知道你是否有过下⾯这些疑问:

  1. 为什么通常在第⼀次访问⼀个站点时,打开速度很慢,当再次访问这个站点时,速度就很快了?
  2. 当登录过⼀个⽹站之后,下次再访问该站点,就已经处于登录状态了,这是怎么做到的呢?

浏览器端发起HTTP请求流程

如果你在浏览器地址栏⾥键⼊极客时间⽹站的地址:http://time.geekbang.org/index.html, 那么接下来,浏览器会完成哪些动作呢?下⾯我们就⼀步⼀步详细“追踪”下。

1. 构建请求

⾸先,浏览器构建请求⾏信息(如下所⽰),构建好后,浏览器准备发起⽹络请求。

GET /index.html HTTP1.1

2. 查找缓存

在真正发起⽹络请求之前,浏览器会先在浏览器缓存中查询是否有要请求的⽂件。其中,浏览器缓存是⼀种在本地保存资源副本,以供下次请求时直接使⽤的技术。

当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载。这样做的好处有:

  • 缓解服务器端压⼒,提升性能(获取资源的耗时更短了);
  • 对于⽹站来说,缓存是实现快速资源加载的重要组成部分。

当然,如果缓存查找失败,就会进⼊⽹络请求过程了。

3. 准备IP地址和端口

浏览器使用HTTP协议作为应⽤层协议,⽤来封装请求的⽂本信息;并使用TCP/IP作传输层协议将它发到网络上,所以在HTTP⼯作开始之前,浏览器需要通过TCP与服务器建⽴连接。也就是说HTTP的内容是通过TCP的传输数据阶段来实现的

在这里插入图片描述

那接下来你可以思考这么“⼀连串”问题:

  • HTTP⽹络请求的第⼀步是做什么呢?结合上图看,是和服务器建⽴TCP连接。

  • 那建⽴连接的信息都有了吗?建⽴TCP连接的第⼀步就是需要准备IP地址和端⼝号。

  • 那怎么获取IP地址和端⼝号呢?这得看看我们现在有什么,我们有⼀个URL地址,那么是否可以利⽤URL地址来获取IP和端⼝信息呢?

数据包都是通过IP地址传输给接收⽅的。由于IP地址是数字标识,⽐如极客时间⽹站的IP是39.106.233.176, 难以记忆,但使⽤极客时间的域名(time.geekbang.org)就好记了,所以基于这个需求⼜出现了⼀个服务,负责把域名和IP地址做⼀⼀映射关系。这套域名映射为IP的系统就叫做“域名系统”,简称DNS(Domain Name System)。

第⼀步浏览器会请求DNS返回域名对应的IP

当然浏览器还提供了DNS数据缓存服务,如果某个域名已经解析过了,那么浏览器会缓存解析的结果,以供下次查询时直接使⽤,这样也会减少⼀次⽹络请求。

拿到IP之后,接下来就需要获取端⼝号了。通常情况下,如果URL没有特别指明端⼝号,那么HTTP协议默认是80端⼝。

4. 等待TCP队列

现在已经把端⼝和IP地址都准备好了,那么下⼀步是不是可以建⽴TCP连接了呢?

答案依然是“不⾏”。Chrome有个机制,同⼀个域名同时最多只能建⽴6个TCP连接,如果在同⼀个域名下同时有10个请求发⽣,那么其中4个请求会进⼊排队等待状态,直⾄进⾏中的请求完成。

如果当前请求数量少于6,会直接进⼊下⼀步,建⽴TCP连接。

5. 建⽴TCP连接

排队等待结束之后,终于可以快乐地和服务器握⼿了,在HTTP⼯作开始之前,浏览器通过TCP与服务器建⽴连接。

6. 发送HTTP请求

⼀旦建⽴了TCP连接,浏览器就可以和服务器进⾏通信了。⽽HTTP中的数据正是在这个通信过程中传输的。

⾸先浏览器会向服务器发送请求⾏,它包括了请求⽅法、请求URI(Uniform Resource Identifier)和HTTP版本协议

发送请求⾏,就是告诉服务器浏览器需要什么资源,最常⽤的请求⽅法是Get。⽐如,直接在浏览器地址栏键⼊极客时间的域名(time.geekbang.org),这就是告诉服务器要Get它的⾸⻚资源。

另外⼀个常⽤的请求⽅法是POST,它⽤于发送⼀些数据给服务器,⽐如登录⼀个⽹站,就需要通过POST⽅法把⽤⼾信息发送给服务器。如果使⽤POST⽅法,那么浏览器还要准备数据给服务器,这⾥准备的数据是通过请求体来发送。

在浏览器发送请求⾏命令之后,还要以请求头形式发送其他⼀些信息,把浏览器的⼀些基础信息告诉服务器。⽐如包含了浏览器所使⽤的操作系统、浏览器内核等信息,以及当前请求的域名信息、浏览器端的Cookie信息,等等。


服务器端处理HTTP请求流程

历经千⾟万苦,HTTP的请求信息终于被送达了服务器。接下来,服务器会根据浏览器的请求信息来准备相应的内容。

1. 返回请求

⾸先服务器会返回响应⾏,包括协议版本和状态码。

但并不是所有的请求都可以被服务器处理的,那么⼀些⽆法处理或者处理出错的信息,怎么办呢?服务器会通过请求⾏的状态码来告诉浏览器它的处理结果,⽐如:

  • 最常⽤的状态码是200,表⽰处理成功;

  • 如果没有找到⻚⾯,则会返回404。

随后,正如浏览器会随同请求发送请求头⼀样,服务器也会随同响应向浏览器发送响应头。响应头包含了服务器⾃⾝的⼀些信息,⽐如服务器⽣成返回数据的时间、返回的数据类型(JSON、HTML、流媒体等类型),以及服务器要在客⼾端保存的Cookie等信息。

发送完响应头后,服务器就可以继续发送响应体的数据,通常,响应体就包含了HTML的实际内容。

以上这些就是服务器响应浏览器的具体过程。

2. 断开连接

通常情况下,⼀旦服务器向客⼾端返回了请求数据,它就要关闭 TCP 连接。不过如果浏览器或者服务器在其头信息中加⼊了:

Connection:Keep-Alive

那么TCP连接在发送后将仍然保持打开状态,这样浏览器就可以继续通过同⼀个TCP连接发送请求。保持TCP连接可以省去下次请求时需要建⽴连接的时间,提升资源加载速度。⽐如,⼀个Web⻚⾯中内嵌的图⽚就都来⾃同⼀个Web站点,如果初始化了⼀个持久连接,你就可以复⽤该连接,以请求其他资源,⽽不需要重新再建⽴新的TCP连接。

3.重定向

到这⾥似乎请求流程快结束了,不过还有⼀种情况你需要了解下,⽐如当你在浏览器中打开geekbang.org后,你会发现最终打开的⻚⾯地址是 https://www.geekbang.org。

这两个URL之所以不⼀样,是因为涉及到了⼀个重定向操作

你可以使⽤curl来查看下请求geekbang.org 会返回什么内容?

在控制台输⼊如下命令:

curl -I geekbang.org

注意这⾥输⼊的参数是-I,和-i不⼀样,-I表⽰只需要获取响应头和响应⾏数据,⽽不需要获取响应体的数据,最终返回的数据如下图所⽰:

在这里插入图片描述

从图中你可以看到,响应⾏返回的状态码是301,状态301就是告诉浏览器,我需要重定向到另外⼀个⽹址,⽽需要重定向的⽹址正是包含在响应头的Location字段中,接下来,浏览器获取Location字段中的地址,并使⽤该地址重新导航,这就是⼀个完整重定向的执⾏流程。这也就解释了为什么输⼊的是geekbang.org,最终打开的却是 https://www.geekbang.org 了。


问题解答

1. 为什么很多站点第⼆次打开速度会很快?

如果第⼆次⻚⾯打开很快,主要原因是第⼀次加载⻚⾯过程中,缓存了⼀些耗时的数据。

那么,哪些数据会被缓存呢?从上⾯介绍的核⼼请求路径可以发现,DNS缓存和⻚⾯资源缓存这两块数据是会被浏览器缓存的。其中,DNS缓存⽐较简单,它主要就是在浏览器本地把对应的IP和域名关联起来,这⾥就不做过多分析了。

下⾯是缓存处理的过程:

在这里插入图片描述

⾸先,我们看下服务器是通过什么⽅式让浏览器缓存数据的?

从上图的第⼀次请求可以看出,当服务器返回HTTP响应头给浏览器时,浏览器是通过响应头中的Cache-Control字段来设置是否缓存该资源。通常,我们还需要为这个资源设置⼀个缓存过期时⻓,⽽这个时⻓是通过Cache-Control中的Max-age参数来设置的,⽐如上图设置的缓存过期时间是2000秒。

Cache-Control:Max-age=2000

这也就意味着,在该缓存资源还未过期的情况下, 如果再次请求该资源,会直接返回缓存中的资源给浏览器。

但如果缓存过期了,浏览器则会继续发起⽹络请求,并且在HTTP请求头中带上:

If-None-Match:"4f80f-13c-3a1xb12a"

服务器收到请求头后,会根据If-None-Match的值来判断请求的资源是否有更新。

  • 如果没有更新,就返回304状态码,相当于服务器告诉浏览器:“这个缓存可以继续使⽤,这次就不重复发送数据给你了。”
  • 如果资源有更新,服务器就直接返回最新资源给浏览器。

简要来说,很多⽹站第⼆次访问能够秒开,是因为这些⽹站把很多资源都缓存在了本地,浏览器缓存直接使⽤本地副本来回应请求,⽽不会产⽣真实的⽹络请求,从⽽节省了时间。同时,DNS数据也被浏览器缓存了,这⼜省去了DNS查询环节。


2. 登录状态是如何保持的?
  • ⽤⼾打开登录⻚⾯,在登录框⾥填⼊⽤⼾名和密码,点击确定按钮。点击按钮会触发⻚⾯脚本⽣成⽤⼾登录信息,然后调⽤POST⽅法提交⽤⼾登录信息给服务器。

  • 服务器接收到浏览器提交的信息之后,查询后台,验证⽤⼾登录信息是否正确,如果正确的话,会⽣成⼀段表⽰⽤⼾⾝份的字符串,并把该字符串写到响应头的Set-Cookie字段⾥,如下所⽰,然后把响应头发送给浏览器。

Set-Cookie: UID=3431uad;

  • 浏览器在接收到服务器的响应头后,开始解析响应头,如果遇到响应头⾥含有Set-Cookie字段的情况,浏览器就会把这个字段信息保存到本地。⽐如把UID=3431uad保持到本地。

  • 当⽤⼾再次访问时,浏览器会发起HTTP请求,但在发起请求之前,浏览器会读取之前保存的Cookie数据,并把数据写进请求头⾥的Cookie字段⾥(如下所⽰),然后浏览器再将请求头发送给服务器。

Cookie: UID=3431uad;

  • 服务器在收到HTTP请求头数据之后,就会查找请求头⾥⾯的“Cookie”字段信息,当查找到包
    含UID=3431uad的信息时,服务器查询后台,并判断该⽤⼾是已登录状态,然后⽣成含有该⽤⼾信息的⻚⾯数据,并把⽣成的数据发送给浏览器。

  • 浏览器在接收到该含有当前⽤⼾的⻚⾯数据后,就可以正确展⽰⽤⼾登录的状态信息了。

在这里插入图片描述

简单地说,如果服务器端发送的响应头内有 Set-Cookie 的字段,那么浏览器就会将该字段的内容保持到本地。当下次客⼾端再往该服务器发送请求时,客⼾端会⾃动在请求头中加⼊ Cookie 值后再发送出去。服务器端发现客⼾端发送过来的Cookie后,会去检查究竟是从哪⼀个客⼾端发来的连接请求,然后对⽐服务器上的记录,最后得到该⽤⼾的状态信息。


总结

详细的“HTTP请求⽰意图”

在这里插入图片描述

如果⼀个⻚⾯的⽹络加载时间过久,你是如何分析卡在哪个阶段的?

1.⾸先猜测最可能的出问题的地⽅,⽹络传输丢包⽐较严重,需要不断重传。然后通过ping curl看看对应的时延⾼不⾼。
2 然后通过wireshake看看具体哪⾥出了问题。
3 假如别⼈访问很快,⾃⼰电脑很慢,就要看看⾃⼰客⼾端是否有问题了。


四、导航流程:从输⼊URL到⻚⾯展⽰,这中间发⽣了什么?

“从输⼊URL到⻚⾯展⽰完整流程⽰意图”:

在这里插入图片描述

从图中可以看出,整个过程需要各个进程之间的配合

先来快速回顾下浏览器进程、渲染进程和⽹络进程的主要职责。

  • 浏览器进程主要负责⽤⼾交互、⼦进程管理和⽂件储存等功能。

  • ⽹络进程是⾯向渲染进程和浏览器进程等提供⽹络下载功能。

  • 渲染进程的主要职责是把从⽹络下载的HTML、JavaScript、CSS、图⽚等资源解析为可以显⽰和交互的⻚⾯。因为渲染进程所有的内容都是通过⽹络获取的,会存在⼀些恶意代码利⽤浏览器漏洞对系统进⾏攻击,所以运⾏在渲染进程⾥⾯的代码是不被信任的。这也是为什么Chrome会让渲染进程运⾏在安全沙箱⾥,就是为了保证系统的安全。

这个过程可以⼤致描述为如下:

  • ⾸先,⽤⼾从浏览器进程⾥输⼊请求信息

  • 然后,⽹络进程发起URL请求

  • 服务器响应URL请求之后,浏览器进程就⼜要开始准备渲染进程了;

  • 渲染进程准备好之后,需要先向渲染进程提交⻚⾯数据,我们称之为提交⽂档阶段;

  • 渲染进程接收完⽂档信息之后,便开始解析⻚⾯和加载⼦资源,完成⻚⾯的渲染。

这其中,⽤⼾发出URL请求到⻚⾯开始解析的这个过程,就叫做导航


从输入URL到页面展示

1. 用户输⼊

当⽤⼾在地址栏中输⼊⼀个查询关键字时,地址栏会判断输⼊的关键字是搜索内容,还是请求的URL

  • 如果是搜索内容,地址栏会使⽤浏览器默认的搜索引擎,来合成新的带搜索关键字的URL。

  • 如果判断输⼊内容符合URL规则,⽐如输⼊的是 time.geekbang.org,那么地址栏会根据规则,把这段内容加上协议,合成为完整的URL,如 https://time.geekbang.org。

当⽤⼾输⼊关键字并键⼊回⻋之后,浏览器便进⼊下图的状态:

在这里插入图片描述

从图中可以看出,当浏览器刚开始加载⼀个地址之后,标签⻚上的图标便进⼊了加载状态。但此时图中⻚⾯显⽰的依然是之前打开的⻚⾯内容,并没⽴即替换为极客时间的⻚⾯。因为需要等待提交⽂档阶段,⻚⾯内容才会被替换。

2. URL请求过程

接下来,便进⼊了⻚⾯资源请求过程。这时,浏览器进程会通过进程间通信(IPC)把URL请求发送⾄⽹络进程,⽹络进程接收到URL请求后,会在这⾥发起真正的URL请求流程。那具体流程是怎样的呢?

⾸先,⽹络进程会查找本地缓存是否缓存了该资源。如果有缓存资源,那么直接返回资源给浏览器进程;如果在缓存中没有查找到资源,那么直接进⼊⽹络请求流程。这请求前的第⼀步是要进⾏DNS解析,以获取请求域名的服务器IP地址。如果请求协议是HTTPS,那么还需要建⽴TLS连接。

接下来就是利⽤IP地址和服务器建⽴TCP连接。连接建⽴之后,浏览器端会构建请求⾏、请求头等信息,并把和该域名相关的Cookie等数据附加到请求头中,然后向服务器发送构建的请求信息。

服务器接收到请求信息后,会根据请求信息⽣成响应数据(包括响应⾏、响应头和响应体等信息),并发给⽹络进程。等⽹络进程接收了响应⾏和响应头之后,就开始解析响应头的内容了。(为了⽅便讲述,下⾯我将服务器返回的响应头和响应⾏统称为响应头。)

(1)重定向

在接收到服务器返回的响应头后,⽹络进程开始解析响应头,如果发现返回的状态码是301或者302,那么说明服务器需要浏览器重定向到其他URL。这时⽹络进程会从响应头的Location字段⾥⾯读取重定向的地址,然后再发起新的HTTP或者HTTPS请求,⼀切⼜重头开始了。

在导航过程中,如果服务器响应⾏的状态码包含了301、302⼀类的跳转信息,浏览器会跳转到新的地址继续导航;如果响应⾏是200,那么表⽰浏览器可以继续处理该请求。

(2)响应数据类型处理

在处理了跳转信息之后,我们继续导航流程的分析。URL请求的数据类型,有时候是⼀个下载类型,有时候是正常的HTML⻚⾯,那么浏览器是如何区分它们呢?

答案是Content-Type。Content-Type是HTTP头中⼀个⾮常重要的字段, 它告诉浏览器服务器返回的响应体数据是什么类型,然后浏览器会根据Content-Type的值来决定如何显⽰响应体的内容。

不同Content-Type的后续处理流程也截然不同。如果Content-Type字段的值被浏览器判断为下载类
型,那么该请求会被提交给浏览器的下载管理器,同时该URL请求的导航流程就此结束。但如果是HTML,那么浏览器则会继续进⾏导航流程。由于Chrome的⻚⾯渲染是运⾏在渲染进程中的,所以接下来就需要准备渲染进程了。

3. 准备渲染进程

默认情况下,Chrome会为每个⻚⾯分配⼀个渲染进程,也就是说,每打开⼀个新⻚⾯就会配套创建⼀个新的渲染进程。但是,也有⼀些例外,在某些情况下,浏览器会让多个⻚⾯直接运⾏在同⼀个渲染进程中。

⽐如我从极客时间的⾸⻚⾥⾯打开了另外⼀个⻚⾯⸺算法训练营,我们看下图的Chrome的任务管理器截图:

在这里插入图片描述

从图中可以看出,打开的这三个⻚⾯都是运⾏在同⼀个渲染进程中,进程ID是23601。

那什么情况下多个⻚⾯会同时运⾏在⼀个渲染进程中呢?

要解决这个问题,我们就需要先了解下什么是同⼀站点(same-site)。具体地讲,我们将“同⼀站点”定义为根域名(例如,geekbang.org)加上协议(例如,https:// 或者http://),还包含了该根域名下的所有⼦域名和不同的端⼝,⽐如下⾯这三个:

https://time.geekbang.org
https://www.geekbang.org
https://www.geekbang.org:8080

它们都是属于同⼀站点,因为它们的协议都是HTTPS,⽽且根域名也都是geekbang.org。

Chrome的默认策略是,每个标签对应⼀个渲染进程。但如果从⼀个⻚⾯打开了另⼀个新⻚⾯,⽽新⻚⾯和当前⻚⾯属于同⼀站点的话,那么新⻚⾯会复⽤⽗⻚⾯的渲染进程。官⽅把这个默认策略叫process-persite-instance。

那若新⻚⾯和当前⻚⾯不属于同⼀站点,情况⼜会发⽣什么样的变化呢?⽐如我通过极客邦⻚⾯⾥的链接打开InfoQ的官⽹(https://www.infoq.cn/ ), 因为infoq.cn和geekbang.org不属于同⼀站点,所以infoq.cn会使⽤⼀个新的渲染进程,你可以参考下图:

在这里插入图片描述

从图中任务管理器可以看出:由于极客邦和极客时间的标签⻚拥有相同的协议和根域名,所以它们属于同⼀站点,并运⾏在同⼀个渲染进程中;⽽infoq.cn的根域名不同于geekbang.org,也就是说InfoQ和极客邦不属于同⼀站点,因此它们会运⾏在两个不同的渲染进程之中。

总结来说,打开⼀个新⻚⾯采⽤的渲染进程策略就是:

  • 通常情况下,打开新的⻚⾯都会使⽤单独的渲染进程;
  • 如果从A⻚⾯打开B⻚⾯,且A和B都属于同⼀站点的话,那么B⻚⾯复⽤A⻚⾯的渲染进程;如果是其他情况,浏览器进程则会为B创建⼀个新的渲染进程。

渲染进程准备好之后,还不能⽴即进⼊⽂档解析状态,因为此时的⽂档数据还在⽹络进程中,并没有提交给渲染进程,所以下⼀步就进⼊了提交⽂档阶段。

4. 提交⽂档

⾸先要明确⼀点,这⾥的“⽂档”是指URL请求的响应体数据。

  • “提交⽂档”的消息是由浏览器进程发出的,渲染进程接收到“提交⽂档”的消息后,会和⽹络进程建⽴传输数据的“管道”。

  • 等⽂档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程。

  • 浏览器进程在收到“确认提交”的消息后,会更新浏览器界⾯状态,包括了安全状态、地址栏的URL、前进后退的历史状态,并更新Web⻚⾯。

更新内容如下图所⽰:

在这里插入图片描述

这也就解释了为什么在浏览器的地址栏⾥⾯输⼊了⼀个地址后,之前的⻚⾯没有⽴⻢消失,⽽是要加载⼀会⼉才会更新⻚⾯。

到这⾥,⼀个完整的导航流程就“⾛”完了,这之后就要进⼊渲染阶段了。

5. 渲染阶段

⼀旦⽂档被提交,渲染进程便开始⻚⾯解析和⼦资源加载了。

⼀旦⻚⾯⽣成完成,渲染进程会发送⼀个消息给浏览器进程,浏览器接收到消息后,会停⽌标签图标上的加载动画。

如下所⽰:

在这里插入图片描述

⾄此,⼀个完整的⻚⾯就⽣成了。那⽂章开头的“从输⼊URL到⻚⾯展⽰,这中间发⽣了什么?”这个过程极其“串联”的问题也就解决了。


总结

  • 服务器可以根据响应头来控制浏览器的⾏为,如跳转、⽹络数据类型判断。

  • Chrome默认采⽤每个标签对应⼀个渲染进程,但是如果两个⻚⾯属于同⼀站点,那这两个标签会使⽤同⼀个渲染进程。

  • 浏览器的导航过程涵盖了从⽤⼾发起请求到提交⽂档给渲染进程的中间所有阶段。

从输⼊URL到⻚⾯展示,这中间发⽣了什么?

1.⽤户输⼊url并回⻋

2.浏览器进程检查url,组装协议,构成完整的url

3.浏览器进程通过进程间通信(IPC)把url请求发送给⽹络进程

4.⽹络进程接收到url请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程

5.如果没有,⽹络进程向web服务器发起http请求(⽹络请求),请求流程如下:
5.1 进⾏DNS解析,获取服务器ip地址,端⼝
5.2 利⽤ip地址和服务器建⽴tcp连接
5.3 构建请求头信息
5.4 发送请求头信息
5.5 服务器响应后,⽹络进程接收响应头和响应信息,并解析响应内容

6.⽹络进程解析响应流程;
6.1 检查状态码,如果是301/302,则需要重定向,从Location⾃动中读取地址,重新进⾏ 第4步,如果是200,则继续处理请求。
6.2 200响应处理:
检查响应类型Content-Type,如果是字节流类型,则将该请求提交给下载管理器,该 导航流程结束,不再进⾏后续的渲染,如果是html则通知浏览器进程准备渲染进程准 备进⾏渲染。

7.准备渲染进程
7.1 浏览器进程检查当前url是否和之前打开的渲染进程根域名是否相同,如果相同,则复⽤ 原来的进程,如果不同,则开启新的渲染进程

8.传输数据、更新状态
8.1 渲染进程准备好后,浏览器向渲染进程发起“提交⽂档”的消息,渲染进程接收到消息和 ⽹络进程建⽴传输数据的“管道”
8.2 渲染进程接收完数据后,向浏览器发送“确认提交”
8.3 浏览器进程接收到确认消息后更新浏览器界⾯状态:安全、地址栏url、前进后退的历史 状态、更新web⻚⾯。

—————————————————————————————————————————

更为详细的答案:
  1. ⽤⼾输⼊URL,浏览器会根据⽤⼾输⼊的信息判断是搜索还是⽹址,如果是搜索内容,就将搜索内容+默认搜索引擎合成新的URL;如果⽤⼾输⼊的内容符合URL规则,浏览器就会根据URL协议,在这段内容上加上协议合成合法的URL
  2. ⽤⼾输⼊完内容,按下回⻋键,浏览器导航栏显⽰loading状态,但是⻚⾯还是呈现前⼀个⻚⾯,这是因为新⻚⾯的响应数据还没有获得
  3. 浏览器进程浏览器构建请求⾏信息,会通过进程间通信(IPC)将URL请求发送给⽹络进程GET /index.html HTTP1.1
  4. ⽹络进程获取到URL,先去本地缓存中查找是否有缓存⽂件,如果有,拦截请求,直接200返回;否则,进⼊⽹络请求过程
  5. ⽹络进程请求DNS返回域名对应的IP和端⼝号,如果之前DNS数据缓存服务缓存过当前域名信息,就会直接返回缓存信息;否则,发起请求获取根据域名解析出来的IP和端⼝号,如果没有端⼝号,http默认80,https默认443。如果是https请求,还需要建⽴TLS连接。
  6. Chrome 有个机制,同⼀个域名同时最多只能建⽴ 6 个TCP 连接,如果在同⼀个域名下同时有 10 个请求发⽣,那么其中 4 个请求会进⼊排队等待状态,直⾄进⾏中的请求完成。如果当前请求数量少于6个,会直接建⽴TCP连接。
  7. TCP三次握⼿建⽴连接,http请求加上TCP头部⸺包括源端⼝号、⽬的程序端⼝号和⽤于校验数据完整性的序号,向下传输
  8. ⽹络层在数据包上加上IP头部⸺包括源IP地址和⽬的IP地址,继续向下传输到底层
  9. 底层通过物理⽹络传输给⽬的服务器主机
  10. ⽬的服务器主机⽹络层接收到数据包,解析出IP头部,识别出数据部分,将解开的数据包向上传输到传输层
  11. ⽬的服务器主机传输层获取到数据包,解析出TCP头部,识别端⼝,将解开的数据包向上传输到应⽤层
  12. 应⽤层HTTP解析请求头和请求体,如果需要重定向,HTTP直接返回HTTP响应数据的状态code301或者302,同时在请求头的Location字段中附上重定向地址,浏览器会根据code和Location进⾏重定向操作;如果不是重定向,⾸先服务器会根据 请求头中的If-None-Match 的值来判断请求的资源是否被更新,如果没有更新,就返回304状态码,相当于告诉浏览器之前的缓存还可以使⽤,就不返回新数据了;否则,返回新数据,200的状态码,并且如果想要浏览器缓存数据的话,就在相应头中加⼊字段:
    Cache-Control:Max-age=2000
    响应数据⼜顺着应⽤层⸺传输层⸺⽹络层⸺⽹络层⸺传输层⸺应⽤层的顺序返回到⽹络进程
  13. 数据传输完成,TCP四次挥⼿断开连接。如果,浏览器或者服务器在HTTP头部加上如下信息,TCP就⼀直保持连接。保持TCP连接可以省下下次需要建⽴连接的时间,提⽰资源加载速度
    Connection:Keep-Alive
  14. ⽹络进程将获取到的数据包进⾏解析,根据响应头中的Content-type来判断响应数据的类型,如果是字节流类型,就将该请求交给下载管理器,该导航流程结束,不再进⾏;如果是text/html类型,就通知浏览器进程获取到⽂档准备渲染
  15. 浏览器进程获取到通知,根据当前⻚⾯B是否是从⻚⾯A打开的并且和⻚⾯A是否是同⼀个站点(根域名和协议⼀样就被认为是同⼀个站点),如果满⾜上述条件,就复⽤之前⽹⻚的进程,否则,新创建⼀个单独的渲染进程
  16. 浏览器会发出“提交⽂档”的消息给渲染进程,渲染进程收到消息后,会和⽹络进程建⽴传输数据的“管道”,⽂档数据传输完成后,渲染进程会返回“确认提交”的消息给浏览器进程
  17. 浏览器收到“确认提交”的消息后,会更新浏览器的⻚⾯状态,包括了安全状态、地址栏的 URL、前进后退的历史状态,并更新web⻚⾯,此时的web⻚⾯是空⽩⻚
  18. 渲染进程对⽂档进⾏⻚⾯解析和⼦资源加载,HTML 通过HTM 解析器转成DOM Tree(⼆叉树类似结构的东西),CSS按照CSS 规则和CSS解释器转成CSSOM TREE,两个tree结合,形成render tree(不包含HTML的具体元素和元素要画的具体位置),通过Layout可以计算出每个元素具体的宽⾼颜⾊位置,结合起来,开始绘制,最后显⽰在屏幕中新⻚⾯显⽰出来。

五、渲染流程(上):HTML、CSS和JavaScript,是如何变成⻚⾯的?

由于渲染机制过于复杂,所以渲染模块在执⾏过程中会被划分为很多⼦阶段,输⼊的HTML经过
这些⼦阶段,最后输出像素。我们把这样的⼀个处理流程叫做渲染流⽔线,其⼤致流程如下图所
示:

在这里插入图片描述

按照渲染的时间顺序,流⽔线可分为如下⼏个⼦阶段:构建DOM树、样式计算、布局阶段、分
层、绘制、分块、光栅化和合成。

在介绍每个阶段的过程中,你应该重点关注以下三点内容:

开始每个⼦阶段都有其输⼊的内容;

然后每个⼦阶段有其处理过程;

最终每个⼦阶段会⽣成输出内容。


构建DOM树

为什么要构建DOM树呢?这是因为浏览器⽆法直接理解和使⽤HTML,所以需要将HTML转换为
浏览器能够理解的结构——DOM树

在这里插入图片描述

从图中可以看出,构建DOM树的输⼊内容是⼀个⾮常简单的HTML⽂件,然后经由HTML解析器
解析,最终输出树状结构的DOM。


样式计算(Recalculate Style)

样式计算的⽬的是为了计算出DOM节点中每个元素的具体样式,这个阶段⼤体可分为三步来完
成。

1. 把CSS转换为浏览器能够理解的结构

那CSS样式的来源主要有哪些呢?你可以先参考下图:

在这里插入图片描述

从图中可以看出,CSS样式来源主要有三种:

  • 通过link引⽤的外部CSS⽂件

  • <style> 标记内的 CSS

  • 元素的style属性内嵌的CSS

和HTML⽂件⼀样,浏览器也是⽆法直接理解这些纯⽂本的CSS样式,所以当渲染引擎接收到
CSS⽂本时,会执⾏⼀个转换操作,将CSS⽂本转换为浏览器可以理解的结构——styleSheets。

为了加深理解,你可以在Chrome控制台中查看其结构,只需要在控制台中输⼊
document.styleSheets,然后就看到如下图所⽰的结构:

在这里插入图片描述


2. 转换样式表中的属性值,使其标准化

现在我们已经把现有的CSS⽂本转化为浏览器可以理解的结构了,那么接下来就要对其进⾏属性
值的标准化操作

要理解什么是属性值标准化,你可以看下⾯这样⼀段CSS⽂本:

body { font-size: 2em }
p {color:blue;}
span {display: none}
div {font-weight: bold}
div p {color:green;}
div {color:red; }

可以看到上⾯的CSS⽂本中有很多属性值,如2em、blue、bold,这些类型数值不容易被渲染引
擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值
标准化。

那标准化后的属性值是什么样⼦的?

在这里插入图片描述

从图中可以看到,2em被解析成了32px,red被解析成了rgb(255,0,0),bold被解析成了700……


3. 计算出DOM树中每个节点的具体样式

现在样式的属性已被标准化了,接下来就需要计算DOM树中每个节点的样式属性了,如何计算
呢?

这就涉及到CSS的继承规则和层叠规则了。

⾸先是CSS继承。CSS继承就是每个DOM节点都包含有⽗节点的样式。这么说可能有点抽象,
我们可以结合具体例⼦,看下⾯这样⼀张样式表是如何应⽤到DOM节点上的

body { font-size: 20px }
p {color:blue;}
span {display: none}
div {font-weight: bold;color:red}
div p {color:green;}

这张样式表最终应⽤到DOM节点的效果如下图所⽰:

在这里插入图片描述

从图中可以看出,所有⼦节点都继承了⽗节点样式。⽐如body节点的font-size属性是20,那
body节点下⾯的所有节点的font-size都等于20。

为了加深你对CSS继承的理解,你可以打开Chrome的“开发者⼯具”,选择第⼀个“element”标
签,再选择“style”⼦标签,你会看到如下界⾯:

在这里插入图片描述

这个界⾯展⽰的信息很丰富,⼤致可描述为如下。

⾸先,可以选择要查看的元素的样式(位于图中的区域2中),在图中的第1个区域中点击对应
的元素元素,就可以了下⾯的区域查看该元素的样式了。⽐如这⾥我们选择的元素是<p>标签,位于html.body.div.这个路径下⾯。

其次,可以从样式来源(位于图中的区域3中)中查看样式的具体来源信息,看看是来源于样
式⽂件,还是来源于UserAgent样式表。这⾥需要特别提下UserAgent样式,它是浏览器提供
的⼀组默认样式,如果你不提供任何样式,默认使⽤的就是UserAgent样式
最后,可以通过区域2和区域3来查看样式继承的具体过程。

样式计算过程中的第⼆个规则是样式层叠。层叠是CSS的⼀个基本特征,它是⼀个定义了如何合
并来⾃多个源的属性值的算法。它在CSS处于核⼼地位,CSS的全称“层叠样式表”正是强调了这
⼀点。

总之,样式计算阶段的⽬的是为了计算出DOM节点中每个元素的具体样式,在计算过程中需要
遵守CSS的继承和层叠两个规则。这个阶段最终输出的内容是每个DOM节点的样式,并被保存
在ComputedStyle的结构内。


布局阶段

现在,我们有DOM树和DOM树中元素的样式,但这还不⾜以显⽰⻚⾯,因为我们还不知道DOM
元素的⼏何位置信息。那么接下来就需要计算出DOM树中可⻅元素的⼏何位置,我们把这个计
算过程叫做布局。

Chrome在布局阶段需要完成两个任务:创建布局树和布局计算。

1. 创建布局树

你可能注意到了DOM树还含有很多不可⻅的元素,⽐如head标签,还有使⽤了display:none属性
的元素。所以在显⽰之前,我们还要额外地构建⼀棵只包含可⻅元素布局树

在这里插入图片描述

从上图可以看出,DOM树中所有不可⻅的节点都没有包含到布局树中。

为了构建布局树,浏览器⼤体上完成了下⾯这些⼯作:

遍历DOM树中的所有可⻅节点,并把这些节点加到布局中;

⽽不可⻅的节点会被布局树忽略掉,如head标签下⾯的全部内容,再⽐如body.p.span这个元
素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。

2. 布局计算

在执⾏布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输⼊内容也是
输出内容,这是布局阶段⼀个不合理的地⽅,因为在布局阶段并没有清晰地将输⼊内容和输出内
容区分开来。针对这个问题,Chrome团队正在重构布局代码,下⼀代布局系统叫LayoutNG,试
图更清晰地分离输⼊和输出,从⽽让新设计的布局算法更加简单。


总结

渲染流程的前三个阶段:DOM⽣成、样式计算和布局。

浏览器不能直接理解HTML数据,所以第⼀步需要将其转换为浏览器能够理解的DOM树结构;

⽣成DOM树后,还需要根据CSS样式表,来计算出DOM树所有节点的样式;

最后计算DOM元素的布局信息,使其都保存在布局树中。

如果下载 CSS ⽂件阻塞了,会阻塞 DOM 树的合成吗?会阻塞⻚⾯的显⽰吗?

不会阻塞DOM 树的合成,但会阻塞⻚⾯的显⽰。

DOM 树和CSSOM树是并⾏⽣成的,两个都完成后才进⾏布局树的⽣成,但如果期间有JS⽂
件,那就需要等待JS⽂件加载并执⾏完成,JS也需要等待CSSOM树⽣成,因为JS可能操作D
OM树和CSSOM树。


六、渲染流程(下):HTML、CSS和JavaScript,是如何变成⻚⾯的?

分层

因为⻚⾯中有很多复杂的效果,如⼀些复杂的3D变换、⻚⾯滚动,或者使⽤z-indexing做z轴排序等,为了更加⽅便地实现这些效果,渲染引擎还需要为特定的节点⽣成专⽤的图层,并⽣成⼀棵对应的图层树(LayerTree)。如果你熟悉PS,相信你会很容易理解图层的概念,正是这些图层叠加在⼀起构成了最终的⻚⾯图像。

要想直观地理解什么是图层,你可以打开Chrome的“开发者⼯具”,选择“Layers”标签,就可以可视化⻚⾯的分层情况,如下图所⽰:

在这里插入图片描述

现在你知道了浏览器的⻚⾯实际上被分成了很多图层,这些图层叠加后合成了最终的⻚⾯。下⾯我们再来看看这些图层和布局树节点之间的关系,如⽂中图所⽰:

在这里插入图片描述

通常情况下,并不是布局树的每个节点都包含⼀个图层,如果⼀个节点没有对应的层,那么这个节点就从属于⽗节点的图层。如上图中的span标签没有专属图层,那么它们就从属于它们的⽗节点图层。但不管怎样,最终每⼀个节点都会直接或者间接地从属于⼀个层。

那么需要满⾜什么条件,渲染引擎才会为特定的节点创建新的层呢?通常满⾜下⾯两点中任意⼀点的元素就可以被提升为单独的⼀个图层。

① 第⼀点,拥有层叠上下⽂属性的元素会被提升为单独的⼀层。

⻚⾯是个⼆维平⾯,但是层叠上下⽂能够让HTML元素具有三维概念,这些HTML元素按照⾃⾝属性的优先级分布在垂直于这个⼆维平⾯的z轴上。你可以结合下图来直观感受下:

在这里插入图片描述

从图中可以看出,明确定位属性的元素、定义透明属性的元素、使⽤CSS滤镜的元素等,都拥有层叠上下⽂属性。

② 第⼆点,需要剪裁(clip)的地⽅也会被创建为图层。

不过⾸先你需要了解什么是剪裁,结合下⾯的HTML代码:

在这里插入图片描述

在这⾥我们把div的⼤⼩限定为200 * 200像素,⽽div⾥⾯的⽂字内容⽐较多,⽂字所显⽰的区域肯定会超出200 * 200的⾯积,这时候就产⽣了剪裁,渲染引擎会把裁剪⽂字内容的⼀部分⽤于显⽰在div区域,下图是运⾏时的执⾏结果:

在这里插入图片描述

出现这种裁剪情况的时候,渲染引擎会为⽂字部分单独创建⼀个层,如果出现滚动条,滚动条也会被提升为单独的层。你可以参考下图:

在这里插入图片描述

所以说,元素有了层叠上下⽂的属性或者需要被剪裁,满⾜这任意⼀点,就会被提升成为单独⼀层。


图层绘制

渲染引擎实现图层的绘制,会把⼀个图层的绘制拆分成很多⼩的绘制指令,然后再把这些指令按照顺序组成⼀个待绘制列表,如下图所⽰:
在这里插入图片描述

从图中可以看出,绘制列表中的指令其实⾮常简单,就是让其执⾏⼀个简单的绘制操作,⽐如绘制粉⾊矩形或者⿊⾊的线等。⽽绘制⼀个元素通常需要好⼏条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表。


栅格化(raster)操作

绘制列表只是⽤来记录绘制顺序和绘制指令的列表,⽽实际上绘制操作是由渲染引擎中的合成线程来完成的。你可以结合下图来看下渲染主线程和合成线程之间的关系:

在这里插入图片描述

如上图所⽰,当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程,那么接下来合成线程是怎么⼯作的呢?

那我们得先来看看什么是视⼝:

通常⼀个⻚⾯可能很⼤,但是⽤⼾只能看到其中的⼀部分,我们把⽤⼾可以看到的这个部分叫做视
口(viewport)。

在有些情况下,有的图层可以很⼤,⽐如有的⻚⾯你使⽤滚动条要滚动好久才能滚动到底部,但是通过视⼝,⽤⼾只能看到⻚⾯的很⼩⼀部分,所以在这种情况下,要绘制出所有图层内容的话,就会产⽣太⼤的开销,⽽且也没有必要。

基于这个原因,合成线程会将图层划分为图块(tile),这些图块的⼤⼩通常是256x256或者512x512,如下图所⽰:

在这里插入图片描述

然后合成线程会按照视⼝附近的图块来优先⽣成位图,实际⽣成位图的操作是由栅格化来执⾏的所谓栅格化,是指将图块转换为位图。⽽图块是栅格化执⾏的最⼩单位。渲染进程维护了⼀个栅格化的线程池,所有的图块栅格化都是在线程池内执⾏的,运⾏⽅式如下图所⽰:

在这里插入图片描述

通常,栅格化过程都会使⽤GPU来加速⽣成,使⽤GPU⽣成位图的过程叫快速栅格化,或者GPU栅格化,⽣成的位图被保存在GPU内存中。

GPU操作是运⾏在GPU进程中,如果栅格化操作使⽤了GPU,那么最终⽣成位图的操作是在
GPU中完成的,这就涉及到了跨进程操作。具体形式你可以参考下图:

在这里插入图片描述

从图中可以看出,渲染进程把⽣成图块的指令发送给GPU,然后在GPU中执⾏⽣成图块的位图,并保存在GPU的内存中。


合成和显示

⼀旦所有图块都被光栅化,合成线程就会⽣成⼀个绘制图块的命令⸺“DrawQuad”,然后将该命令提交给浏览器进程。

浏览器进程⾥⾯有⼀个叫viz的组件,⽤来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其⻚⾯内容绘制到内存中,最后再将内存显⽰在屏幕上。

到这⾥,经过这⼀系列的阶段,编写好的HTML、CSS、JavaScript等⽂件,经过浏览器就会显⽰出漂亮的⻚⾯了。


渲染流水线大总结

在这里插入图片描述

结合上图,⼀个完整的渲染流程⼤致可总结为如下:

  1. 渲染进程将HTML内容转换为能够读懂的DOM树结构。

  2. 渲染引擎将CSS样式表转化为浏览器可以理解的styleSheets,计算出DOM节点的样式。

  3. 创建布局树,并计算元素的布局信息。

  4. 对布局树进⾏分层,并⽣成分层树

  5. 为每个图层⽣成绘制列表,并将其提交到合成线程。

  6. 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。

  7. 合成线程发送绘制图块命令DrawQuad给浏览器进程。

  8. 浏览器进程根据DrawQuad消息生成页面,并显示到显⽰器上。


相关概念

1. 更新了元素的几何属性(重排)

你可先参考下图:

在这里插入图片描述

从上图可以看出,如果你通过JavaScript或者CSS修改元素的⼏何位置属性,例如改变元素的宽度、⾼度等,那么浏览器会触发重新布局,解析之后的⼀系列⼦阶段,这个过程就叫重排。⽆疑,重排需要更新完整的渲染流⽔线,所以开销也是最⼤的


2. 更新元素的绘制属性(重绘)

接下来,我们再来看看重绘,⽐如通过JavaScript更改某些元素的背景颜⾊,渲染流⽔线会怎样调整呢?你可以参考下图:

在这里插入图片描述

从图中可以看出,如果修改了元素的背景颜⾊,那么布局阶段将不会被执⾏,因为并没有引起⼏何位置的变换,所以就直接进⼊了绘制阶段,然后执⾏之后的⼀系列⼦阶段,这个过程就叫重绘相较于重排操作,重绘省去了布局和分层阶段,所以执⾏效率会⽐重排操作要⾼⼀些


3. 直接合成阶段

那如果你更改⼀个既不要布局也不要绘制的属性,会发⽣什么变化呢?渲染引擎将跳过布局和绘制,只执⾏后续的合成操作,我们把这个过程叫做合成。具体流程参考下图:

在这里插入图片描述

在上图中,我们使⽤了CSS的transform来实现动画效果,这可以避开重排和重绘阶段,直接在⾮主线程上执⾏合成动画操作。这样的效率是最⾼的,因为是在⾮主线程上合成,并没有占⽤主线程的资源,另外也避开了布局和绘制两个⼦阶段,所以相对于重绘和重排,合成能⼤⼤提升绘制效率


总结

哪些具体的实践⽅法能减少重绘、重排呢?

1 触发repaint reflow的操作尽量放在⼀起,⽐如改变dom⾼度和设置margin分开写,可能会出 发两次重排
2 通过虚拟dom层计算出操作总得差异,⼀起提交给浏览器。之前还⽤过 createdocumentfragment来汇总append的dom,来减少触发重排重绘次数。

  1. 使⽤ class 操作样式,⽽不是频繁操作 style
  2. 避免使⽤ table 布局
  3. 批量dom 操作,例如 createDocumentFragment,或者使⽤框架,例如 React
  4. Debounce window resize 事件
  5. 对 dom 属性的读写要分离
  6. will-change: transform 做优化
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序媛小y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值