一、页面渲染的完整流程
1、页面渲染的完整流程
浏览器从用户输入网址之后到底做了什么呢?而从输入到呈现可以分为两个部分:网络通信和页面渲染
网络通信
一个DNS域名解析的视频地址:http://vd3.bdstatic.com/mda-iihdr04swzhj4wiy/mda-iihdr04swzhj4wiy.mp4?playlist=%5B%22hd%22%2C%22sc%22%5D
-
1)用户输入url并敲击回车。
-
2)域名解析阶段: 如果用户输入的是ip地址则直接进入第三条。DNS解析的时候,浏览器会首先搜索浏览器自身的DNS缓存。如果有那么直接使用,如果没有那么就向本地配置的首选DNS服务器发送域名解析请求,(方式:通过UDP发送请求到53端口)。
首选DNS服务器【运营商服务器】会先查询自己的DNS缓冲,如果找到就直接返回,否则代替我们浏览器发送迭代请求,首先会去找根域名服务器,(请问www.xxxx.com这个域名的IP地址是多少啊?)因为根域名服务器值存储了13个根域的DNS的IP地址。所以它只知道顶级域名com的服务器地址,就告诉运营商的DNS我不知道这个域名的IP地址,但是我知道com域的IP地址,你去找它去。
运营商DNS服务器就去找com顶级域名服务器,(请问www.xxxx.com这个域名的IP地址是多少?)顶级域名服务器,告诉它说,我不知道这个的IP但是我知道xxxx.com的域名服务器地址,你可以去找他。
运营商DNS服务器就去找xxxx.com二级域名服务器,(请问www.xxxx.com这个域名的IP地址是多少?),它查了一下,找到了,于是告诉了www.xxxx.com的IP地址给了运营商域名服务器,候运营商的DNS服务器就拿到了www.xxxx.com这个域名对应的IP地址,并返回给Windows系统内核,内核又把结果返回给浏览器,终于浏览器拿到了www.xxxx.com对应的IP地,这次dns解析圆满成功。
-
3)TCP发起三次握手阶段
向服务器的WEB程序80端口发起TCP的连接请求,要经过三次握手才能建立成功。
第一步:A向B发送请求报文,同步位SYN=1(表示需要返回响应),seq = x,x为要传输数据时候的第一个数据字节的序号第二步:B收到后,如果同意则发送确认信号,同步位SYN=1,ACK = 1(表示该报文是确认报文),自己的序号seq = y,确认序号ack = x + 1;第三步:A收到后回发送确认,这时候没有了SYN表示不需要回复,ACK = 1表示该条是确认报文,seq = x + 1表示要传输的字节序号,同时ack确认号为y + 1;此时A 的 TCP 通知上层应用进程,连接已经建立。 B 的 TCP 收到主机 A 的确认后,也通知其上层应用进程:TCP 连接已经建立。复制代码
客户端: 喂能听到我说话吗?服务器: 能,你能听到我说话吗?客户端: 很清楚呢,那我们开始聊聊吧。复制代码
-
- 建立TCP连接后发起http请求
-
- 服务器端响应http请求,浏览器得到html代码.
以上是网络通信部分,接下来将会对页面渲染部分进行叙述。当浏览器拿到html后是如何进行页面渲染的
页面渲染
-
1)HTML文档解析,构建DOM树。DOM树的构建是一个深度遍历的过程,当前节点的所有子节 点都构建完成以后,才会去构建当前节点的下一个兄弟节点。
-
2)将CSS解析成CSSOM树(CSS Rule Tree)。遇到
css
样式如link
标签或者style
标签时开始解析css
,构建样式树。HTML解析 构建和CSS的解析是相互独立的并不会造成冲突,因此我们通常将css
样式放在head
中, 让浏览器尽早解析css
; -
3)当html的解析遇到script标签会怎样呢? 答案是停止DOM树的解析开始下载js。因为js是会阻塞html解析的,是阻塞资源。其原因在于js可能会改变html现有结构。例如有的节点是用js动态构建的,在这种情况下就会停止dom树的构建开始下载解析js。
脚本在文档的何处插入,就在何处执行。当 HTML 解析器遇到一个 script 标记时,它会暂停构建 DOM,将控制权移交给 JavaScript 引擎;等 JavaScript 引擎运行完毕,浏览器会从中断的地方恢复 DOM 构建。
而因此就会推迟页面首绘的时间。可以在首绘不需要js的情况下用async和defer实现异步加载。这样js就不会阻塞html的解析了。
-
4)根据DOM树和CSSOM树,来构建Render Tree(渲染树),注意渲染树,并不等于DOM树,因为一些像head或display:none的东西,就没有必要放在渲染树中了。
- 5)有了Render Tree,浏览器已经能知道网页中有哪些节点,各个节点的CSS定义,以及它们的从属关系,下一步操作就是Layout,顾名思义,就是计算出每个节点在屏幕中的位置。布局使用流模型的Layout算法。Layout是一个递归的过程,每个节点都负责自己及其子节点的Layout。Layout结果是相对父节点的坐标和尺寸。其过程可以简述为:
父节点确定自己的宽度父节点完成子节点放置,确定其相对坐标节点确定自己的宽度和高度父节点根据所有的子节点高度计算自己的高度复制代码
- 6)Layout后,浏览器已经知道哪些节点要显示,每个节点的CSS属性是什么,每个节点在屏幕中的位置是哪里,就进入了最后一步painting,按照算出来的规则,通过显卡,把内容画到屏幕上。
2、HTML解析
DOM树的构建过程为:将字节转换成字符,确定tokens标记,将tokens转换成节点,然后构建 DOM 树。
3、CSS解析
CSSOM的创建过程: 字节 => 字符 => TOken => Node => CSSOM;
在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。
注意:CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。
css样式的优先级同样是件麻烦的事情。
!important > 内联样式 > 内部样式表{ID选择器>Class选择器>标签选择器} > 外部样式表复制代码
3、渲染树
样式树和DOM树连接在一起形成一个渲染树,渲染树用来计算可见元素的布局并且作为将像素渲染到屏幕上的过程
渲染树布局
布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。
渲染树绘制
在绘制阶段,遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。
以上我们详细介绍了浏览器工作流程中的重要步骤,接下来我们讨论几个相关的问题:
二、渲染过程中遇到JS文件怎么处理?
JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性(下文会介绍这两者的区别)。
JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建。
原本DOM和CSSOM的构建是互不影响,井水不犯河水,但是一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建完毕后,DOM再恢复DOM构建。
这是什么情况?
这是因为JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。前面我们介绍,不完整的CSSOM是无法使用的,但JavaScript中想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。
三、你真的了解回流和重绘吗
我们知道,当网页生成的时候,至少会渲染一次。在用户访问的过程中,还会不断重新渲染。重新渲染会重复上图中的第四步(回流)+第五步(重绘)或者只有第五个步(重绘)。重绘:当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,而不会影响布局的,比如background-color。 回流:当render tree中的一部分(或全部)因为元素的规模尺寸、布局、隐藏等改变而需要重新构建 回流必定会发生重绘,重绘不一定会引发回流。重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。
1)常见引起回流属性和方法
添加或者删除可见的DOM元素;元素尺寸改变——边距、填充、边框、宽度和高度内容变化,比如用户在input框中输入文字浏览器窗口尺寸改变——resize事件发生时计算 offsetWidth 和 offsetHeight 属性设置 style 属性的值复制代码
2)常见引起重绘属性和方法
栗子:
3)如何减少回流、重绘
1、使用transform 代替 top等2、尽量少使用table布局,因为可能一点点的改动就会导致重绘制3、动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame4、CSS 选择符从右往左匹配查找,避免节点层级过多5、使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)6、复制代码
四、async和defer的作用是什么?有什么区别?
-
没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。
-
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
-
有 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
五、为什么操作 DOM 慢
因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。
六、渲染页面时常见哪些不良现象?
FOUC:由于浏览器渲染机制(比如firefox),再CSS加载之前,先呈现了HTML,就会导致展示出无样式内容,然后样式突然呈现的现象;
白屏:有些浏览器渲染机制(比如chrome)要先构建DOM树和CSSOM树,构建完成后再进行渲染,如果CSS部分放在HTML尾部,由于CSS未加载完成,浏览器迟迟未渲染,从而导致白屏;也可能是把js文件放在头部,脚本会阻塞后面内容的呈现,脚本会阻塞其后组件的下载,出现白屏问题。
七、页面优化方案
- 1、减少资源请求的次数和压缩数据内容。因为资源的请求是一个复杂的过程。网速相同的条件下,下载一个100KB的图片比下载两个50KB的图片要快。所以,请减少HTTP请求。
①进行资源打包,如利用webpbck对引入的多个文件进行打包,减少请求次数。 ②使用雪碧图,可以避免因不同图片引起的多次资源下载
- 2、高效合理的css选择符可以减轻浏览器的解析负担。因为css是逆向解析的所以应当避免多层嵌套。
避免使用通配规则。如 *{} 计算次数惊人!只对需要用到的元素进行选择 尽量少的去对标签进行选择,而是用class。如:#nav li{},可以为li加上nav_item的类名,如下选择.nav_item{} 不要去用标签限定ID或者类选择符。如:ul#nav,应该简化为#nav 尽量少的去使用后代选择器,降低选择器的权重值。后代选择器的开销是最高的,尽量将选择器的深度降到最低,最高不要超过三层,更多的使用类来关联每一个标签元素。 考虑继承。了解哪些属性是可以通过继承而来的,然后避免对这些属性重复指定规则复制代码
- 3、从js层面谈页面优化
①解决渲染阻塞 如果在解析HTML标记时,浏览器遇到了JavaScript,解析会停止。只有在该脚本执行完毕后,HTML渲染才会继续进行。所以这阻塞了页面的渲染。 解决方法:在标签中使用 async或defer特性 ②减少对DOM的操作 对DOM操作的代价是高昂的,这在网页应用中的通常是一个性能瓶颈。 解决办法:修改和访问DOM元素会造成页面的Repaint和Reflow,循环对DOM操作更是罪恶的行为。所以请合理的使用JavaScript变量储存内容,考虑大量DOM元素中循环的性能开销,在循环结束时一次性写入。 减少对DOM元素的查询和修改,查询时可将其赋值给局部变量。 ③使用JSON格式来进行数据交换 JSON是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,是理想的数据交换格式。同时,JSON是 JavaScript原生格式,这意味着在 JavaScript 中处理 JSON数据不需要任何特殊的 API 或工具包。 ④让需要经常改动的节点脱离文档流 因为重绘有时确实不可避免,所以只能尽可能限制重绘的影响范围。复制代码
- 4、使用CDN加速(内容分发网络)
其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。
- 5、 精简CSS和JS文件
如使用 YUI Compressor
压缩工具,它的特点是:移除注释;移除额外的空格;细微优化;标识符替换。
八、Load 和 DOMContentLoad
Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。
DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载。