一、浏览器
浏览器是用来检索、展示以及传递Web信息资源的应用程序。Web信息资源由统一资源标识符( Uniform Resource Identifier,URI)所标记,它是一张网页、一张图片、一段视频或者任何在Web上所呈现的内容。使用者可以借助超级链接( Hyperlinks),通过浏览器浏览互相关联的信息。总之,览器的主要功能就是将用户输入的url,转化为用户可以与之交互的界面。
二、进程与线程
进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。 与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
浏览器进程

浏览器设置的时候是一个多进程模型,这样能确保浏览的安全性和稳定性。如何一个页面有问题,不影响其他页面的运行。
浏览器进程。主要负责界面显示、用户交互、子进程管理、同时提供存储等功能。
渲染进程。 核心任务是将HTML、CSS和JavaScript转换为用户可以与之交互的网页,排版引擎Blink和JavaScript引擎V8都运行在该进程中,默认情况下,Chrome为每一个Tab标签页创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下的。
GPU进程。GPU图形处理器(英语:graphics processing unit,缩写:GPU),负责3D css效果,网页,Chrome ui的绘制。
网络进程。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立处理,成为单独一个进程。
插件进程。主要负责插件的运行,因为插件易崩溃,所以通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响

三、浏览器渲染
输入url地址到浏览器显示页面发生了什么

从上图可以看到,整个过程需要各个进程之间的配合。 1、浏览器进程接收到用户输入的URL请求,浏览器进程便将URL转发给网络进程 2、网络进程中发起真正的URL请求 3、网络进程接收到响应头数据,便解析响应头数据,并将数据转发给浏览器进程。 4、浏览器进程接收到网络进程的响应头数据之后,发送"提交文档"消息到渲染进程; 5、渲染进程接收到"提交文档"的消息之后,便开始准备接收HTML数据,接收数据的方式是直接和网络进程建立数据管道。 6、等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程; 7、浏览器进程接收到渲染进程"确认提交"的消息之后,便开始移除之前旧的文档,然后更新浏览器进程中的页面状态。 所谓提交文档,就是浏览器主进程,将网络进程接收到的HTML数据提交给渲染进程。
浏览器渲染流程编写好HTML、CSS、JavaScript文件之后,经过浏览器就会显示出漂亮的页面,它是如何转化的? 渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的 HTML 经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线,其大致流程如下图所示:

以一个简单的例子来讲解渲染流水线,首先我们生产一个这样的页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>渲染流程</title>
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<p><span>重点介绍</span>渲染流程</p>
<div>
<p>green</p>
</div>
<div class="nav">
<div class="opacity">red</div>
</div>
</body>
</html># css/index.css
body{
font-size: 20px;
background-color: lavender;
}
.nav{
width:200px;
height: 200px;
background-color: red;
position: fixed;
right:10px;
bottom: 10px;;
}
.opacity{
opacity: .5;
width:100px;
height: 100px;
background-color: gray;
position: absolute;
z-index: 100;
}
body{
position: relative;
}1、构建DOM树
DOM解析的特点,是不会被阻塞的。 因为浏览器无法直接理解和使用HTML,所以需要将HTML转化为浏览器能够理解的结构--DOM树。

从上图可以看出,树结构很像我们现实生活中的"树",其中的每一个点我们称为节点,相连的节点称为父子节点。在浏览器渲染中,我们使用的就是树结构。 接下来,我们来看看DOM树的构建过程,一个html页面就是一个典型的树结构

DOM树输入内容就是一个简单的HTML页面,然后经过HTML解析器,最终输出树状结构的DOM。 为了更直观的理解DOM树,打开Chrome的"开发者工具",选择"Console"标签来打开控制台,接着在控制台中输入"document"后回车,就可以看到一个完整的DOM树结构,如下图:

图中document就是DOM结构,DOM和HTML的内容几乎是一样的额,但是和HTML不同的是,DOM是保存在内存中的树状结构,可以通过JavaScript来进行查询和修改内容。
我们现在已经生成了DOM树,但是DOM节点的样式我们依然不知道,要让DOM节点拥有正确的样式,这就需要样式计算。
2、样式计算(Recalculate Style)
先有内容,我们才能对内容就行修饰。 样式计算的目的是为了计算出DOM节点中每一个元素的具体样式,这个阶段大体分三步。
把css转换为浏览器内容理解的结构和HTML文件一样,浏览器也是无法直接理解这些纯文本的CSS样式,所以当渲染引擎接收到CSS文本的时,会执行一个转换操作,将css文本转换为浏览器可以理解的结构--styleSheets。

渲染引擎会把获取到的 CSS 文本全部转换为 styleSheets 结构中的数据,并且该结构同时具备了查询和修改功能,这会为后面使用JS的样式操作提供基础。
转换样式表中的属性值,使其标准化将 2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。

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

3、布局阶段
现在,有DOM树和DOM树中元素的样式,但是还足以显示页面,因为我们还不知道DOM元素的几何位置,那么接下来就需要计算出DOM树中可见元素的几何位置,我们把这个计算过程叫做布局。 Chrome在布局阶段需要完成两个任务:创建布局树和布局计算。
创建布局树DOM树有些元素不会在页面上显示,被用户看到,如head标签和使用了display:none的元素。所以在显示之前,我么还要额外地构建一棵只包含了可见元素的布局树。

从上图可以看出,DOM树中所有不可见的节点都没有有包含到布局树中。
布局计算我们已经有了一棵完整的布局树,那么接下来就要根据DOM节点对应的css树中的样式,计算布局树节点的坐标位置。即计算元素在视口上确切的位置和大小。
4、图层树
有了布局树之后,每个元素的具体位置信息都计算出来了,那么接下俩是不是就要开始着手绘制页面了?不是。因为页面中有很多复杂的效果,如一些复杂的3D转换,页面滚动,或者使用z-index,为了更方便的实现这些效果,渲染引擎还需要为特定的节点生成专门的图层,并生成一棵对应的图层树(LayerTree)。这和PS的图层类似,正是这些图层叠加在一起才最终构成了页面图像。

实际上,浏览器的页面被分成了很多图层,这些图层叠加在一起后,最终合成了页面。
布局树和图层树的关系

通常情况下,并不是布局树中的每一个节点都包含一个图层,如果一个节点没有对应的图层,那么这个节点就从属于父节点的图层。那么什么情况满足,渲染引擎才会为特定的节点创建新的图层呢?满足一下两个条件中的任意一个,元素就可以被单独提升为一个图层。 1,拥有层叠上下文属性的元素会被提升为单独的一层。
1、position:fixed 2、css 3d 例如:transform:rotateX(30deg) 3、video 4、canvas 5、有css3动画的节点
2,需要剪裁的地方也会被创建为图层,文字内容溢出div的情况
图层的绘制在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,那么接下来我们看看渲染引擎是如何实现图层的绘制? 如果给你一张纸,让你先把背景涂成暗色,然后再中间中间位置花一个红色的圆,最后在圆上画一个绿色三角,你会怎么操作,通常你会按顺序操作。 渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分为很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示:

从图中可以看出,绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如说绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表。
栅格化操作绘制列表指令用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成。结合下图看渲染主线程和合成线程之间的关系:

如上图所示,当图层的绘制列表准备好之后,主线程会把该绘制列表提交给合成线程。
比如说,一个图层很大,页面需要滚动底部,才能全部显示。但是通过视口,用户只能看到页面很小的一部分,所以在此种情况下,要一次性绘制完图层所有的内容,会产生很大的开销,且没有必要。
基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小通常是256256或512512。然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作就是有栅格化来执行的。所谓栅格化,是指将图块转化为位图(所谓位图就是能够看的到的图层区域)。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行,运行方式如下图所示:

通常,栅格化过程都会使用GPU来加速生成,使用GPU生成位图过程叫快速栅格化,或者GPU栅格化,生成的位图被保存在GPU内存中。GPU操作是运行在GPU进程中的,那么栅格化,还涉及到了跨进程操作。

从图中可以看出,渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。
合成和显示
一旦所有图块被栅格化(又称光栅化),合成线程就会生成一个绘制图块的命令--“DrawQuad”,然后将该命令提交给浏览器进程。
浏览器进程里有一个叫viz的组件,用来接收合成线程发过来的DrawQuad命令,然后根据DrawQuad命令,将其页面内容绘制到内存中,最后显示在屏幕上。
到此,经过一系列的阶段,编写好的HTML、CSS、JavaScript等文件,经过浏览器就会显示为页面。
五、渲染流水线总结

结合上图,一个完整的渲染流程大致可总结如下: 1、渲染进程将HTML内容转换为浏览器能够读懂的DOM树结构。 2、渲染引擎将CSS样式表转化为浏览器能够理解的css树,计算出DOM节点的样式。 3、DOM树+css树创建布局树,并计算元素的布局信息。 4、对布局树进行分层,并生成图层树。 5、对每个图层生成绘制列表,并将其提交给合成线程。 6、对每个图层进行单独的绘制 7、合并图层。
六、重绘和重排
更新元素的几何属性(重排)如果你通过JS或css修改元素的几何位置属性,如width,height等,那么会触发浏览器的重新布局,解析之后的一系列子阶段,这个过程就叫重排。重排需要更新完整的渲染流水线,所以开销也最大的。
更新元素的绘制属性(重绘)修改元素的背景色,布局阶段不会执行,因为没有引起几何位置的变换,所以直接进入绘制,然后执行之后的一系列子阶段,这个过程就叫重绘。相较重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排效率高。