页面展现过程
本篇文章将从用户使用过程的角度描述一个网页是如何在浏览器中被展现出来。
输入网址
当用户想要访问网站时,大部分会在浏览器中输入网址,浏览器在接收到用户输入的网址后,就会开始寻找资源,这个过程我们分为四个部分来说明。
-
域名解析
域名解析是为了寻找到域名对应的ip地址,浏览器在得到域名后首先会在缓存中进行搜索是否有该域名,如果有的话就可以直接得到ip地址,浏览器按照浏览器缓存 -> 系统缓存 -> 路由器缓存 -> ISP(网络运营商)缓存 这样的顺序进行搜索,如果这些缓存中都没有该域名,浏览器就要向DNS服务器发出查找请求,查找该域名所对应的IP地址。在域名解析步骤后,浏览器就会成功得到域名所对应的IP地址。
-
访问服务器对应端口
在拿到IP地址后,浏览器就可以成功的找到该域名对应的服务器,根据用户输入的信息访问对应的服务器端口。
-
生成资源文件并返回
在访问对应的服务器端口后,根据用户输入的信息,服务器会执行对应网页的代码逻辑,将模版和数据合成为一个资源文件并返回给浏览器。
-
浏览器开始渲染
在浏览器得到返回的资源文件之后,就会开始进行渲染,之后网页便会在浏览器中呈现给用户了。
浏览器渲染
对于一个HTML资源文件,浏览器是有着明确的渲染机制的,在描述渲染机制之前,我们需要了解以下几个名词。
- DOM树
- DOM(Document Object Model)叫做文档对象模型,在浏览器解析整个HTML文档后会生成DOM树,这个DOM树中包含页面的所有内容,这个内容指的是页面的结构内容。
- CSSOM树
- CSSOM(CSS Object Model)叫做CSS对象模型,在浏览器解析完HTML文档中所有的CSS资源后会生成一个CSSOM树,这个树中包含了页面所有的样式。
- 需要注意的是CSSOM树的构建过程会阻塞渲染,因为CSS是层叠的,规则可以被覆盖,所以在CSSOM树构建完成之前渲染会被阻塞,下一节本文会解释两种阻塞
- 渲染树
- 渲染树同时包含内容与样式,DOM树与CSSOM树结合之后会生成渲染树。
了解了这几个名词后本文将展示浏览器的渲染流程,浏览器的渲染流程大致可分为五步:
- 浏览器解析HTML,生成DOM树
- 浏览器解析CSS,生成CSSOM树
- 浏览器根据DOM树和CSSOM树生成渲染树
- 浏览器根据渲染树对页面进行布局,计算每个节点的几何结构
- 浏览器根据布局将各个像素绘制到页面上
需要注意的是在浏览器解析HTML的过程中,也就是在第一步中,遇到CSS资源和非阻塞资源请求时浏览器可以在不影响解析的情况下进行资源下载与解析,也就是说CSSOM树的构建并不会影响DOM树的生成,所以第一步和第二步是可以同时进行的。但是,当遇到<script>
标签的时候,解析会被阻塞,直到脚本被解析完毕。关于对JavaScript的解析阻塞问题在本文之后也会被提到。
用一张图片总结一下浏览器渲染的过程:
关于Reflow和Repaint
先来解释一下什么是Reflow和Repaint
- Repaint是重绘,顾名思义就是重新绘制,对于页面变化的部分进行重新绘制,不会影响布局
- Reflow是重流,我们知道HTML使用的是流式布局,那都重流了肯定是要重新布局了,那Reflow的话就是要重新计算每个节点的几何尺寸, 位置然后再绘图,成本比Repaint要高上很多。
所以我们可以推论出只要会影响布局结构的,都会触发Reflow,例如DOM节点的增删改,对盒模型属性margin padding 和 border的更改。还有一点,就是CSS中的display: none;
这条声明,会直接把节点从DOM树中移除,所以也会触发Reflow。
Reflow一定会Repaint,这个我们从上面的渲染流程就可以推出。而只改变一些表面上的样式,不影响布局结构的,都只会触发Repaint,比如改个颜色。
那么了解了这两个问题之后,我们想要做优化的话可以这样:
首先我们最好一次性把样式修改好,而不是一条一条的去修改;其次我们可以把DOM离线后进行修改,再显示出来,节省成本;然后我们在做动画时可以给动画使用绝对定位,这样不会触发reflow。欢迎大家在评论分享更多的优化思路。
阻塞问题
在上个部分本文提到过浏览器渲染时会遇到的阻塞问题,在这个部分展开说说。
阻塞大概分为两种阻塞:渲染阻塞和解析阻塞
- 渲染阻塞发生时,非阻塞资源(例如img)和CSS文件资源请求不会停止,浏览器会继续下载这些资源,阻塞点后面的标签会继续被解析,但是JS的代码不会被解析执行,页面渲染也不会被触发。
- 解析阻塞发生时,阻塞点后面的标签不会被继续解析,但是外部资源可以正常加载
CSS的加载会导致渲染阻塞,这个我们结合文章上面讲的渲染步骤也可以推断出原因,因为浏览器要把整个CSSOM树渲染完之后才能进入渲染树的生成,所以CSS不加载完渲染也不会开始。
JS下载后会立即执行,这会导致解析阻塞,这个是由于浏览器需要一个稳定的DOM树,而JS可以改变DOM树,所以需要等JS加载并执行后得到一个稳定的结构才能继续往下解析[2]。
这两个问题会导致网页显示时的问题:
- 白屏问题,这个问题一般是由于head中有大量的CSS资源或者JS资源阻塞了渲染和解析,这些阻塞浏览器需要解析的时间太长,导致刚打开网页时出现了白屏。
- FOUC(文档样式短暂失效)问题,这个问题是由于CSS加载时间过长或者放在了文档底部,加上一些浏览器的渲染机制问题,在CSS加载好之前先呈现出了HTML结构内容[3],在CSS加载好之后样式才生效。
对于这两个问题的优化思路,第一是减少CSS和JS的体积,加快加载速度;第二是调整资源引入的位置,对于白屏问题的优化最好把JS的引入位置放在body的尾部,对于FOUC问题最好把CSS的引入位置放在head中;第三就是异步请求JS,缩短JS的加载时间,关于异步请求在之后本文也会提到。
异步加载JS
异步加载的两个方式分别是async和defer,引入JS时在<script>
标签中写入这两个中的一个便可以异步加载JavaScript,如果不选择异步加载,那么浏览器在解析加载JavaScript的时候会串行下载执行。下面本文来解释一下这两种方式的区别:
- async:使用async的时候,对JS文件进行加载时就不会阻塞页面解析渲染了,但是一个比较大的问题就是JS文件在被加载完后就会立即执行,我们无法控制JS文件执行的时机
- defer:使用defer的时候,JS文件的下载会被浏览器并行执行,并在所有元素解析完成之后按照顺序执行
结语
本文首先以用户访问网站角度切入,描述了从网址到网页展现的过程,随后讲述了浏览器渲染的流程,然后解释了Reflow、Repaint以及资源加载的阻塞问题。根据阻塞问题,本文引出了异步加载并针对两种方式给出了解释。
本文纯粹为个人学习所得的一些总结,其中也参考了一些经验文章,如果有什么问题欢迎评论指出,共同进步一起成长。
引用
[1] ‘必须明白的浏览器渲染机制 - 掘金,’ https://juejin.cn/post/6844903846834094094
[2] ‘浏览器加载和渲染html的顺序-css渲染效率的探究_席飞剑的博客-CSDN博客,’ https://blog.csdn.net/xifeijian/article/details/10813339
[3] ‘FOUC是啥?FOUC和首屏白屏问题 - 掘金,’ https://juejin.cn/post/7083681659584774181