进程包涵线程,
微信是一个进程, 里面有很多诸如用户登录等线程.
进程与进程之间是相互独立的, 他们各自有各自的内存, 而线程之间是独立的, 但他们共享同一个内存空间.
没有进程, 线程就不存在, 需要进程来控制多个线程的执行.
如果一个页面有问题,不影响其他页面的运行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9583XeF9-1644134091925)(星期五.assets/image-20220204161822885.png)]
HTML
、CSS
和JavaScript
转换为**「用户可以与之交互的网页」,排版引擎Blink
和JavaScript
引擎V8都运行在该进程中,默认情况下,Chrome
为每一个Tab
标签页创建一个渲染进程。出于安全考虑,渲染进程都是运行在「沙箱模式」**下的。所以我们开启一个页面,至少会启动4个进程。
构造请求
首先,浏览器构造请求行,构建好之后,浏览器准备发起网络请求
查找缓存
在发起请求前, 还要确认一下是否有缓存, 如果本来就有这个请求的缓存的话, 浏览器会拦截请求,返回该资源的副本,并直接结束请求。
这样可以**「缓解服务的压力,提升性能」**。如果缓存查找失败,则进入网络请求。
准备IP地址和端口
浏览器使用 HTTP 协议在应用层上封装请求的文本信息, 之后用 TCP/IP 协议作传输层协议传到网络上,
正因如此, 在 HTTP 开始工作前, 浏览器先要与服务器建立 TCP/IP 连接。也就是说HTTP的内容是通过TCP的传输数据阶段来实现的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RcUe3ryv-1644134091926)(星期五.assets/image-20220204162913595.png)]
数据包是通过 IP 地址传输给接收方的, 但是 IP 地址太难记了, 用域名来记更方便, 就出现了 DNS (域名系统) , 来为 ’ IP 地址 ’ 和 ’ 域名地址 ’ 做一次映射.
第一步浏览器会请求 DNS 返回域名对应的 IP。「当然浏览器还提供了 DNS 数据缓存服务」.
「等待TCP队列」
准备好 IP 和端口之后, 浏览器还有个检查机制, 因为浏览器规定一个域名的TCP链接不能超过六个, 如果少于六个, 就进入下一步, 建立tcp链接
排队等待结束后,建立TCP连接
通常情况下,一旦服务器向客户端返回了请求数据,它就要关闭TCP连接Connection:Keep-Alive
之后讲, 有了这个, 就不需要重复建立新的TCP连接。浏览器可以继续通过同一个TCP连接发送请求。不会关闭TCP链接
因为DNS和页面资源被缓存了: 「DNS缓存」和「页面资源缓存」
浏览器通过响应头的Cache-Control
字段来设置是否缓存该资源。
Cache-Control:Max-age=2000 //缓存过期时间是2000
但如果缓存过期了,浏览器则会继续发送网络请求,并且在HTTP
请求头中带上:
If-None-Match:"4f80f-13c-3a1xb12a"
简要来说,很多网站第二次访问能够秒开,是因为浏览器缓存直接使用本地副本来回应请求,而不会产生真实的网络请求,DNS
数据也被浏览器缓存了,这又省去了 DNS
查询环节。
**重定向:**重定向的意思就是说浏览器里面指定访问的一个url地址,去网站服务器里面访问后,
网站的服务器又指向浏览器中另外的一个地址,然后浏览器再去访问网站服务器的那个另外的地址。
提交文档:所谓提交文档,就是「浏览器主进程」,「将网络进程接收到的HTML数据提交给渲染进程」。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2hxQpJqi-1644134091927)(星期五.assets/image-20220206141216703.png)]
1、**「浏览器进程」接收到用户输入的URL请求,「浏览器进程」**便将URL转发给网络进程。
2、**「网络进程」**中发起真正的URL请求。
3、「网络进程」接收到响应头数据,便解析响应头数据,并将数据转发给「浏览器进程」。
4、「浏览器进程」接收到网络进程的响应头数据之后,发送"「提交文档」"消息到**「渲染进程」**。
5、「渲染进程」接收到"提交文档"的消息之后,便开始准备接收HTML数据,接收数据的方式是直接和「网络进程」建立「数据管道。」
6、等文档数据传输完成之后,「渲染进程」会返回“确认提交”的消息给「浏览器进程」。
7、「浏览器进程」接收到「渲染进程」"确认提交"的消息之后,便开始移除之前旧的文档,然后更新**「浏览器进程」**中的页面状态。
HTML,DOM,样式计算,布局,图层,绘制,栅格化,合成和显示。
一个完整的渲染流程大致可总结如下:
渲染进程将HTML内容转换为浏览器能够读懂的**「DOM树」**结构。
渲染引擎将CSS样式表转化为浏览器能够理解的**「CSS树」**,计算出DOM节点的样式。分三步
将css文本转换为浏览器能理解的样式表
标准化样式表中的属性值(比如bule变成rgb值之类的)
根据CSS
的**「继承规则」和「层叠规则」**计算每个dom节点的样式
继承规则就是继承祖先节点的文本和字体样式之类的,比如body { font-size: 20px }
层叠规则就是计算样式权重
DOM树 + CSS树创建布局树,并计算元素的布局信息。
所有不可见的节点都没有包含到布局树中。
计算布局树节点的坐标位置。即计算元素在视口上确切的位置和大小。
对布局树进行分层,并生成**「图层树」**。
因为页面中有很多复杂的效果,如一些**「复杂的3D转换」,「页面滚动」**,或者使用z-index
,为了更方便的实现这些效果,「渲染引擎还需要为特定的节点生成专门的图层,并生成一棵对应的图层树(LayerTree)」。
不是所有节点都会有图层,这时候看满不满足两个要求
对每个**「图层」生成「绘制列表」**,并将其提交给合成线程。
渲染引擎把一个图层的绘制拆分为很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表并提交给合成线程
对每个图层进行单独的绘制
合并线程的主场:根据视图划分图层为图块,按照视口附近的图块来优先生成位图,也就是优先GPU栅格化,并保存在GPU中
合并图层。
一旦所有图块被栅格化,合成线程就会生成一个绘制图块的命令—“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里的viz组件接收该指令并将页面内容绘制在内存中,最后显示在屏幕上
接下来我们从一个简单的html页面来谈浏览器的渲染流程:
HTML内容转换为浏览器DOM树结构的过程:字节 → 字符 → 令牌 → 节点 → 对象模型。
当解析器发现非阻塞资源,例如一张图片,浏览器会请求这些资源并且继续解析。当遇到一个CSS文件时,解析也可以继续进行,但是对于标签(特别是没有
async
或者 defer
属性)会阻塞渲染并停止HTML的解析。
浏览器构建DOM树时,这个过程占用了主线程。当这种情况发生时,**「预加载扫描仪」**将解析可用的内容并请求高优先级资源,如CSS、JavaScript和web字体。多亏了预加载扫描器,我们不必等到解析器找到对外部资源的引用来请求它。它将在后台检索资源,以便在主HTML解析器到达请求的资源时,它们可能已经在运行,或者已经被下载。预加载扫描仪提供的优化减少了阻塞。
样式计算的目的是为了计算出DOM节点中每一个元素的具体样式,这个阶段大体分三步。
将css文本转换为浏览器能理解的样式表
标准化样式表中的属性值(比如bule变成rgb值之类的)
根据CSS
的**「继承规则」和「层叠规则」**计算每个dom节点的样式
继承规则就是继承祖先节点的文本和字体样式之类的,比如body { font-size: 20px }
层叠规则就是计算样式权重
CSS
来源有:
link
引用的CSS
文件style
标签内的CSS
style
属性内嵌的CSS
和HTML文件一样,浏览器也是无法直接理解这些纯文本的CSS样式,所以**「当渲染引擎接收到CSS文本的时,会执行一个转换操作,将css文本转换为浏览器可以理解的结构—styleSheets。」**
渲染引擎会把获取到的 CSS
文本全部转换为 styleSheets
结构中的数据,并且该结构同时具备了查询和修改功能,这会为后面使用JS
的样式操作提供基础。
我们已经将CSS
转换为浏览器能理解的结构了,那么接下来就要对其进行属性值的标准化操作。
body { font-size: 2em }
p {color:blue;}
span {display: none}
div {font-weight: bold}
div p {color:green;}
div {color:red; }
所以需要将所有值如 2em
、blue
、bold
,转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。
这里就涉及到CSS
的**「继承规则」和「层叠规则」**了。
样式计算阶段的目的是为了计算出 DOM
节点中每个元素的具体样式,在计算过程中需要遵守 CSS
的继承和层叠两个规则。这个阶段最终输出的内容是每个 DOM
节点的样式,并被保存在 ComputedStyle
的结构内。
每个 DOM
元素最终的计算样式,可以打开 Chrome
的“开发者工具”,选择第一个“element”标签,然后再选择“Computed”子标签
现在,我们有DOM树和DOM树中元素的样式,但是还足以显示页面,因为我们还不知道DOM元素的几何位置,那么接下来就需要**「计算出DOM树中可见元素的几何位置,我们把这个计算过程叫做布局」**。
Chrome
在布局阶段需要完成两个任务:
DOM树有些元素不会在页面上显示,被用户看到,如head
标签和使用了display:none
的元素。所以在显示之前,我么还要额外地构建一棵**「只包含了可见元素的布局树」**。
从上图可以看出,DOM树中所有不可见的节点都没有有包含到布局树中。
我们已经有了一棵完整的布局树,那么接下来就要根据DOM节点对应的CSS
树中的样式,计算布局树节点的坐标位置。即计算元素在视口上确切的位置和大小。
有了布局树之后,每个元素的具体位置信息都计算出来了,那么接下来是不是就要开始着手绘制页面了?不是。因为页面中有很多复杂的效果,如一些**「复杂的3D转换」,「页面滚动」,或者使用z-index
,为了更方便的实现这些效果,「渲染引擎还需要为特定的节点生成专门的图层,并生成一棵对应的图层树(LayerTree)」**。这和PS的图层类似,正是这些图层叠加在一起才最终构成了页面图像。
想要直观的理解什么是图层,可以打开Chrome
的"开发工具",选择Layers
标签,就可以查看可视化页面的分层情况。
「布局树和图层树的关系」
通常情况下,并不是布局树中的每一个节点都包含一个图层,如果一个节点没有对应的图层,那么这个节点就从属于父节点的图层。那么什么情况满足,渲染引擎才会为特定的节点创建新的图层呢?满足一下两个条件中的任意一个,元素就可以被单独提升为一个图层。
页面是一个二维平面,但层叠上下文能够上HTML
元素拥有三维概念,这些HTML
元素按自身属性的优先级分布在垂直于这个二维平面的Z轴上,以下情况会作为单独的图层。
position:fixed
css 3d
例如:transform:rotateX(30deg)
video
canvas
CSS3
动画的节点will-change
那么什么是剪裁,结合以下代码
Document
所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下图:
从上图我们可以看到,document层上有A和B层,而B层之上又有两个图层。这些图层组织在一起也是一颗树状结构。
图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。
这里我么把div的大小限定为200 * 200像素,而div里面的文字内容比较多,文字所显示的区域肯定会超过200 * 200的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在div区域,下面是运行时的执行结果:
出现这种裁剪情况时,渲染引擎会为文字单独为文字创建一层,如出现滚动条,滚动条也会被提升为单独的层。
在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,那么接下来我们看看渲染引擎是如何实现图层的绘制?
如果给你一张纸,让你先把背景涂成暗色,然后再中间中间位置花一个红色的圆,最后在圆上画一个绿色三角,你会怎么操作,通常你会按顺序操作。
渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分为很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示:
从图中可以看出,绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如说绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在**「图层绘制阶段,输出的内容就是这些待绘制列表」**。
栅格化,就是指将图块转化为位图。
合成线程接收到上面的绘制列表后的工作:因为视口,合成线程会把图层划分多个图块,然后每个图块按离视口的距离被优先交由GPU栅格化生成位图,并保存在GPU中
绘制列表指令用来记录绘制顺序和绘制指令的列表,而实际上**「绘制操作是由渲染引擎中的合成线程来完成」**。结合下图看渲染主线程和合成线程之间的关系:
如上图所示,当图层的绘制列表准备好之后,主线程会把该绘制列表提交给合成线程,那么合成线程是如何工作的?
首先我们谈一个概念,「视口」。什么是视口?
通常一个页面可能很大,用户只能看到其中的一部分,我们把**「用户可以看到的这个区域叫视口(viewport)。」**
比如说,一个图层很大,页面需要滚动底部,才能全部显示。但是通过视口,用户只能看到页面很小的一部分,所以在此种情况下,要一次性绘制完图层所有的内容,会产生很大的开销,且没有必要。
基于这个原因,「合成线程会将图层划分为图块」,这些图块的大小通常是256 * 256或512 * 512。然后**「合成线程会按照视口附近的图块来优先生成位图」**,实际生成位图的操作就是有栅格化来执行的。所谓栅格化,**是指将图块转化为位图(所谓位图就是能够看的到的图层区域)。而图块是栅格化执行的最小单位。**渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行,运行方式如下图所示:
通常,栅格化过程都会使用GPU来加速生成,「使用GPU生成位图过程叫快速栅格化,或者GPU栅格化」,生成的位图被保存在GPU内存中。GPU操作是运行在GPU进程中的,那么栅格化,还涉及到了跨进程操作。
从图中可以看出,渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。
一旦所有图块被栅格化,合成线程就会生成一个绘制图块的命令—“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里的viz组件接收该指令并将页面内容绘制在内存中,最后显示在屏幕上
到此,经过一系列的阶段,编写好的HTML
、CSS
、JavaScript
等文件,经过浏览器就会显示为页面。
重排:几何位置的改变,width等,也称回流,需要更新完整的渲染流水线,开销最大
重绘:绘制属性的改变,背景颜色等,省去了布局和分层阶段
合成:比如css的transition动画,更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局、分层和绘制,只执行后续的合成操作,
从上图可以看出,如果你**「通过JS或CSS修改元素的几何位置属性」,如width
,height
等,那么会触发浏览器的重新布局,解析之后的一系列子阶段,这个过程就叫重排也称回流。「重排需要更新完整的渲染流水线,所以开销也最大的。」
比如通过JS更改某些元素的背景颜色,渲染流水的调整参见下图:
修改元素的背景色,布局阶段不会执行,因为**「没有引起几何位置的变换」,所以直接进入绘制,然后执行之后的一系列子阶段,这个过程就叫「重绘」。相较重排操作,「重绘省去了布局和分层阶段,所以执行效率会比重排效率高。」**
那如果你更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做**「合成。」**
在上图,我们使用CSS
的transform
来实现动画效果,可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率最高,因为是在非主线程上合成的,并没有占用主线程的资源。
如果我们要提升性能,需要做的就是减少浏览器的重绘和回流
display: none
,操作结束后再把它显示出来。因为在display
属性为none
的元素上进行的DOM操作不会引发回流和重绘。