前言
本文总结了从输入一个URL地址到浏览器完成渲染的整个过程,并将关联的知识点进行详细描述。
全文文字描述,可能会比较繁琐,请耐心观看。
第一次总结知识点,如果有写的不对的地方,请批评改正。
1、从输入一个URL地址到浏览器完成渲染的整个过程
总体流程
1、输入地址
2、查找域名对应的IP地址
3、建立TCP连接
4、发送HTTP请求
5、服务器返回HTTP响应
6、浏览器解析渲染页面
7、断开连接
详细流程
可以分为两个阶段:导航阶段、渲染阶段
一、导航阶段
这一阶段又分为4个步骤:
1、用户输入
2、URL请求
3、准备渲染进程
4、提交文档
1. 用户输入
浏览器会判断地址栏输入的关键字类别,是搜索内容还是请求的URL。
- 如果是搜索内容,地址栏会使用默认的搜索引擎,来合成新的带搜索关键字的URL。
- 如果输入的内容符合URL规则,那么地址栏会根据规则,把内容加上协议等,合成完整的URL。
- 同时,标签页上的图标进入加载状态。浏览器进程会将信息传递给网络进程。
2. URL请求:
这一步就比较重点,涉及到许多网络知识。
- 首先网络进程会根据用户的输入查找本地缓存是否有该资源。如果有缓存直接返回给浏览器进程,进入渲染阶段。如果没有就进入下一步。(涉及知识点:浏览器的缓存策略)
- 接着就是进行DNS解析,所谓的DNS解析,其目的就是获取请求域名对应的服务器IP地址。(涉及知识点:DNS过程详解)
- 当获取到目标的IP地址后,就可以和服务器进行TCP连接,就是著名的三次握手。(涉及知识点:TCP连接)
- 建立连接后,浏览器就可以发送请求,这个时候浏览器端会构建请求行、请求头等消息,并把和该域名相关的Cookie等数据附加到请求头中,然后向服务器发送构建的请求信息。(涉及知识点:HTTP请求)
- 等服务器接收到请求信息后,会根据信息生成响应数据,并返回。
- 网络进程接受到服务器响应内容后开始进行解析。(涉及知识点:HTTP响应)
3、准备渲染进程:
准备渲染进程时会分成两种情况
- 通常情况下,打开新的页面会使用单独的渲染进程。
- 如果从A页面打开B页面,并且A和B都属于同一站点的话,那么B页面会复用A页面的渲染进程。
- 值得注意的是,此时数据还在网络进程中,并没有提交给渲染进程,所以下一步就进入了提交文档步骤。
4、提交文档:
- 提交文档的消息是由浏览器进程发出的,渲染进程收到“提交文档”的消息后,会和网络进程建立传输数据的“管道”。
- 等文档数据传输完成之后,渲染进程会返回“确认提交”的消息给浏览器进程。
- 浏览器进程收到“确认提交”的消息后,会更新浏览器界面状态,包括:安全状态、地址栏的URL、前进后退的历史状态、并更新Web页面。
以上导航阶段的步骤完成后,就进入渲染阶段
渲染阶段分为7个步骤:
1、构建DOM树
2、构建CSSOM树
3、加载或执行JS脚本
4、构建渲染树renderTree
5、分层 + 图层绘制
6、分块 + 栅格化
7、合成和显示
1、构建DOM树
构建DOM的原因是因为浏览器无法直接理解和使用HTML,所以需要将HTML转换成浏览器能够理解的结构--DOM树。
- 这个步骤输入的内容是HTML文件,由HTML解析器解析后,生成树状结构的DOM节点。
2、构建CSSOM树
主要计算的是每个DOM节点中元素的具体样式。
- 输入内容是css文件,可以是link引入的外部CSS文件,标记内的CSS属性,以及元素style属性内嵌的CSS属性。
- 解析的过程也是将CSS文件转化成浏览器能够理解的styleSheets,对样式表中的属性值进行转换,使其标准化,然后再计算出DOM树中的每个节点的具体样式,这时会考虑到CSS的继承规则和层叠规则。
- 最后生成类似于DOM树的CSSOM树。
3、加载或执行JS脚本
加载JS脚本和CSS文件时都会阻塞渲染。
4、构建渲染树renderTree
- 根据DOM树和CSSOM树会生成一个渲染树render Tree。
- 根据渲染树,会对树中的节点进行计算,确定每个节点在页面中的宽高和位置,最终确定布局,这一流程称为回流。
布局结束后,渲染进程会进行分层、图层绘制、分块、栅格化、最后到合成和显示。
5、分层 + 图层绘制
分层的主要目的是渲染引擎需要为特定的节点生成专用的图层,并生成一颗对应的图层树LayerTree。
- 输入的内容是布局树,处理的主要对象是拥有层叠上下文属性的元素和需要裁剪的地方(超出容器的内容被隐藏或出现滚动条),这些内容都会被提升为单独的一层图层。
- 图层绘制的目的是渲染引擎对图层树中的每个图层进行绘制。
6、分块 + 栅格化
- 分块的概念是若一个页面很大的时候,一下子全部渲染会导致渲染时间过长。通过视口,用户只能看到很小的一部分,为了提升性能,合成线程会按照视口附近的图块优先生成位图,实际生成位图的操作是由栅格化来执行的。
- 栅格化是指将图块转换成位图,图块是栅格化执行的最小单位,生成的位图保存在GPU内存中。
7、合成和显示
最后当所有图块被栅格化,合成线程就会生成一个绘制图块的命令,浏览器接收到该命令后,将页面的内容绘制到内存中,最后将内存显示在屏幕上。
2、重绘和回流是什么?
重绘Repaint
当元素样式的改变不影响布局时,浏览器会将新样式赋予给元素并重新绘制它,对应的就是分层、图层绘制、分块、栅格化以及合成的过程。例如:color、background-color、visibility等。
回流Reflow
当渲染树中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程。对应的就是构建DOM树、CSSOM树以及合成渲染树的过程。
导致回流的操作:
- 页面首次渲染。
- 浏览器窗口大小发生改变。
- 元素尺寸或位置发生改变。
- 元素内容发生改变。
- 元素字体发生改变。
- 添加或删除可见的DOM元素。
- 激活CSS伪类。
- 查询某些属性或调用某些方法,比如clientWidth、clientHeight、offsetWidth、offsetHeight、scrollWidth、scrollHeight、scrollTo()等等。
性能影响:
回流比重绘的代价更高,因为回流会导致重绘,但是重绘不一定引起回流。而且有时回流一个单一的元素,会导致其父元素和与之相关的元素也产生回流。
避免回流:
-
CSS方面:
- 避免使用table布局。
- 尽可能在DOM树的最末端改变class。
- 避免设置多层内联样式。
- 将动画效果应用到position属性为absolute或fixed的元素上。
-
JS方面:
-
避免频繁操作DOM,最好是统一操作。
-
避免频繁操作样式,最好一次性重写。
-
也可以先为元素设置display:none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
-
避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
-
对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
-
3、浏览器的缓存机制
概念:
浏览器缓存是指浏览器会将从服务器请求回来的资源进行存储,等下次访问的时候判断存储的状态是否能够使用后,决定该次资源的获取方式:再次请求服务器 or 使用缓存资源。
目的:
对于一个请求来说,资源的流转过程大致可理解为:
浏览器发起网络请求-后端对请求进行处理-服务器响应
浏览器缓存的目的或者说作用在于,可以帮助优化请求和响应的性能。
如果可以使用缓存,就减少了请求时间,避免重复请求,优化整个页面的渲染时间。
缓存的位置:
浏览器缓存的位置主要可以分为Memory Cache、Disk Cache。
比如:
- 200 from memory cache,代表不访问服务器,直接从内存中读缓存。
- 200 from disk cache,代表不访问服务器,直接从磁盘中读缓存。
1. Memory Cache
主要是包含当前页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等,其特点在于 - 读取速度速度比disk cache快
- 缓存持续性短
- 会随着浏览器进程的释放而释放(一旦关闭Tab栏,内存中的缓存就会被释放)
2. Disk Cache
主要是大文件和系统内存使用率高的文件,其特点在于 - 读取速度慢点
- 比memory cache存储时间长,可存储的东西多 在所有浏览器缓存中,disk cache覆盖面基本是最大的
三级缓存原理(访问缓存优先级):
- 先在内存中查找,如果有的话直接加载
- 如果内存中不存在,就到硬盘中查找。
- 如果硬盘中也没有,就进行网络请求,请求到的资源缓存到硬盘和内存中。
缓存机制:
浏览器和服务器之间的通信方式为:request-response。
- 浏览器发起HTTP请求时,先判断是否有缓存。
- 如果没有,向服务器请求,响应的请求结果以及缓存标识存入缓存中,并加载页面。
- 如果有,则判断是否命中强缓存。
- 如果命中强缓存,则读取缓存,返回200+缓存信息,加载页面。
- 如果没有命中强缓存,则发送请求,判断是否命中协商缓存。
- 如果命中协商缓存,且资源可用,则读取缓存,返回缓存,加载页面。
- 如果没有命中协商缓存,则读取响应,并将资源存储缓存中。
缓存的策略:
缓存可以分为强缓存和协商缓存
浏览器向服务器请求资源时,首先判断是否命中强缓存,再根据请求的响应结果判断是否命中协商缓存。
1、强缓存:
浏览器在加载资源时,会先根据本地缓存资源的请求头中的信息判断是否命中强缓存,如果命中,则直接返回缓存中的资源不会再向服务器发送请求。
强缓存中判断请求头的信息是Expires和Cache-Control
-
Expires: 是http1.0时的规范,它的值是一个绝对时间的GMT格式的时间字符串。这个时间代表着这个资源的失效时间,在此时间之前就可以命中缓存。
这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差度较大时,就会导致缓存混乱。 -
Cache-Control: 是http1.1时出现的,主要是利用该字段的max-age值来进行判断,它是一个相对时间,例如Cache-Control:max-age=3600,代表着资源的有效期是3600秒。
Cache-Control常用的设置值:
no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。
no-store:禁止使用缓存,每一次都要重新请求数据。
public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。
private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。Cache-Control与Expires可以在服务端配置启用,同时启用的时候,Cache-Control优先级高,在某些不支持HTTP1.1的环境下,Expires会生效。
2、协商缓存:
当强缓存没有命中的时候,浏览器会发送一个请求到服务器,服务器根据请求头中的信息来判断是否命中缓存,如果命中,则返回304,告诉服务器资源没有更新,可以使用本地缓存。
协商缓存策略中的请求头信息是Last-Modify / If-Modify-Since和ETag / If-None-Match
-
Last-Modify/If-Modify-Since
浏览器第一次请求一个资源的时候,服务器会在响应头中加上Last-Modify,值是这个资源在服务器上最后的修改时间,浏览器接收后缓存文件和响应头。
浏览器下一次请求这个资源,当检测到有Last-Modify字段,就会添加If-Modify-Since的请求头字段,值是Last-Modify的值。
当服务器收到这个资源请求,判断里面有If-Modify-Since的值,会和服务器中这个资源最后的修改时间对比,如果没有变化,响应304和空的响应体,意思是让浏览器直接使用缓存。如果If-Modify-Since时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是响应200和新的资源文件。
缺点:- 短时间内资源发生了改变,Last-Modify并不会发生变化
- 如果这个资源在一个周期内修改回原来的样子,正常来说应该是可以使用缓存,但是按时间的就没办法辨别,所以就产生了新的策略ETag。
-
ETag/If-None—Match
与Last-Modify/If-Modify-Since不同的是,ETag/If-None-Match返回的是一个校验码。
ETag是服务器响应请求时返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,ETag就会重新生成。
浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到请求头里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。 -
两者的区别:
- 精确度上:ETag优于Last-Modify。
- 性能上:Last-Modify由于ETag,因为Last-Modify只需要记录时间,而ETag需要服务器去计算一个hash值。
- 优先级上:服务器校验优先考虑ETag。
应用场景
- 1、频繁变动的资源:Cache-Control: no-cache
使用协商缓存 - 2、不常变化的资源:Cache-Control: max-age=31536000
处理不常变化的资源时,给Cache-Control配置一个很大的时间,这样浏览器之后请求相同的URL会命中强缓存。而为了解决更新的问题,就需要在文件名中添加hash、版本号等动态字符,之后更改动态字符从而达到更改引用URL的目的,让之前的强缓存失效。
用户行为对浏览器缓存的影响
- 1、打开网址,在地址栏输入地址
查找disk cache中是否有匹配,如果有则匹配,如果没有则发送网络请求。 - 2、普通刷新(F5)
因为Tab栏并没有关闭,因此Memory Cache时可用的,所以会被优先使用,其次会查找Disk Cache,换句话说强缓存无效,而协商缓存有效。 - 3、强制刷新(Ctrl + F5)
浏览器不适用缓存,因此发送的请求头均带有Cache-Control:no-cache,强缓存无效,而且协商缓存无效。
4、DNS服务
什么是DNS?
DNS全称Domain Name System,即域名系统。
DNS是一个由分层的DNS服务器实现的分布式数据库;一个使得主机能够查询分布式数据库的应用层协议。
DNS协议运行在UDP之上。
DNS的服务
- DNS解析
DNS解析就是通过域名,最终得到该域名对应的IP地址的过程。 - 主机别名(host aliasing)
有着复杂域名的主机能拥有一个或者多个别名,当主机别名存在时,可能比域名规范名更加容易记忆,所以DNS可以通过获取主机别名来找到对应的规范域名以及对应的IP地址。 - 邮件服务器别名(mail server aliasing)
同样的,电子邮箱地址虽然现在看上去简单,但是其邮件服务器的域名可能会很复杂。电子邮件程序可以调用DNS,对提供的域名进行解析,获取该地址的规范域名以及其IP地址。 - 负载分配(load distribution)
DNS也用于在冗余的服务器之间进行负载分配。繁忙的站点被冗余分布在多台服务器上,每台服务器均运行在不同的端系统上,每个都有着不同的IP地址。由于这些冗余的Web服务器,一个IP地址集合因此与同一个规范域名相联系。DNS数据库中存储着这些IP地址集合,当用户对映射到某地址集合的名字发出一个DNS请求时,该服务器用IP地址的整个集合进行响应,这样就等于DNS会在所有冗余的Web服务器之间循环分配了负载。
DNS缓存
DNS解析是带有缓存的,因为IP地址是唯一的,缓存能减少查找时间。
一般来说DNS缓存会存在于浏览器缓存、操作系统缓存、本地hosts文件、部分路由器缓存、本地DNS服务器缓存、根域名DNS服务器缓存。
查询过程:
- 先搜索浏览器自身的DNS缓存
- 尝试读取操作系统的hosts文件
- 部分路由器会存储DNS缓存
- 查找本地DNS服务器
- 查找根域名DNS服务器
5、CSS、JS是否阻塞渲染?
首先结论是CSS、JS都会阻塞页面的渲染。
CSS
一般来说,浏览器的渲染会经过几个流程:
- 构建DOM Tree - 构建CSS Rule Tree - 将DOM Tree和CSS Rule Tree合成为Render Tree。
CSS阻塞页面渲染
对于页面来说,渲染的时候需要两种元素,一种是DOM Tree,一种是CSS Rule Tree。换句话说,如果在CSS加载过程中,就无法生成CSS Rule Tree,自然也就会阻塞渲染。
CSS不会阻塞DOM解析
根据上面的流程图,以及浏览器渲染的经过,不难看出:构建DOM树,是和构建CSS Rule Tree并行发生的,所以CSS的加载并不会影响HTML解析和DOM树的构成。
JavaScript
JS脚本和CSS文件不同的是,JS脚本不仅会阻塞页面的渲染,还会阻塞DOM的解析。
当浏览器加载中遇到JS脚本,由于JS脚本会操作DOM,所以会导致暂停构建DOM,去下载并运行JS脚本,直到脚本运行完成,然后继续构建DOM。
每次去执行JS脚本都会严重地阻塞DOM树的构建,如果JS脚本还操作了CSSOM,而正好这个CSSOM还没有下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和构建。
所以,JS脚本的位置很重要,之前的策略都是将JS脚本放在HTML的最后位置,这样,当程序运行到JS脚本的位置时,DOM构建已经完成,就不会去阻塞DOM的构建过程。
现在有新的script标签属性:defer、async
script标签中的defer和async
之前的script标签中并没有,在HTML5中,<script>标签新增的两个属性defer和async
defer:加载后续文档元素的过程将和 <script> 的加载并行进行(异步),但是 <script> 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
下载是异步的,执行需等待DOM解析完成后。
async:加载和渲染后续文档元素的过程将和 <script> 的加载与执行并行进行(异步)。
下载是异步的,下载完就执行,执行会阻塞DOM解析。