浏览器页面渲染机制及简单优化
浏览器的内核是指支持浏览器运行的最核心的程序,分为两个部分:
1:渲染引擎
2:JS引擎
目前市面上常见的浏览器内核可以分为这四种:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)
页面加载过程要点如下:
1:浏览器根据DNS服务器得到域名的IP地址
2:向这个IP的机器发送HTTP请求
3:服务器收到、处理并返回HTTP请求
4:浏览器得到返回内容
浏览器渲染过程:
1:浏览器会解析三个东西:
①、HTML/SVG/XHTML,HTML字符串描述了一个页面的结构,浏览器会把HTML结构字符串解析转换DOM树形结构
②、解析CSS会产生CSS规则树,他和DOM结构比较像
③、Javascript脚本,等到Javascript脚本文件加载后,通过DOM API和CSSOM API来操作DOM Tree和Rule Tree
2:解析完成后,浏览器引擎会通过DOM Tree和CSS Rule Tree来构造Redering Tree
①、Rendering Tree渲染树并不等同于DOM树,渲染树只会包括需要显示的节点和这些节点的样式信息
②、CSS的Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element(也就是每个Frame)
③、然后,计算每个Frame的位置,这又叫layout和reflow过程
3:最后通过调用操作系统Native GUI的API绘制
构建DOM:
1:浏览器读取HTML的原始字节,并根据 文件的制定编码(例如UTF-8)将他们转换成字符串
2:将字符串转换成Token
3:生成节点对象并构建DOM
构建CSSOM:
DOM会捕获页面的内容,但浏览器还需要知道页面如何展示,所以需要构建CSSOM。(与构建DOM的过程相似)
注意:CSS匹配HTML元素是一个相当复杂和有性能问题的事情,所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。
构建渲染树:
当我们生成DOM树和CSSOM树以后,就需要将这两棵树组合为渲染树。在这一过程中,不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式,如果某个节点是display:none的,那么久不会再渲染树中显示。
浏览器如果渲染过程中遇到JS文件怎么处理:
渲染过程中,如果遇到<script>就停止渲染,执行JS代码。因为浏览器渲染和JS执行共用一个线程,而且这里必须是单线程操作,多线程会产生渲染DOM冲突。JavaScript的加载、解析与执行回阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
也就是说,如果你想首屏渲染的越快,就越不应该在首屏中加载JS文件,这也是都建议将script标签放在body标签底部的原因,当然在当下,并不是说script标签必须放在底部,因为你可以给script标签添加defer或者async属性
JS文件不只是阻塞DOM的构建,他也会导致CSSOM也阻塞DOM的构建:
因为JavaScript不只是可以改DOM,还可以更改样式,也就是他可以更爱CSSOM。因为不完整的CSSOM是无法使用的,如果JavaScript想访问CSSOM并更改他,那么在执行JavsScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器善为完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至完成CSSOM的下载和构建。也就是说,在这种情况下,刘安琪会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。
布局与绘制:
当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。布局流程的输出是一个“盒模型”,它会精确的捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。布局完成后,浏览器会立即发出“Paint Setup”和"Paint"时间,将渲染树转换成屏幕上的像素
补充说明:
1、async和defer的作用是什么?有什么区别
①:<script src="script.js"></script>
没有defer或async,浏览器会立即加载并执行制定的脚本,也就是说不等后续载入的文档元素,读到就加载并执行。
②:<script async src="script.js"></script>
async属性表示异步执行引入的JavaScript,与defer的区别在于,如果已经加载好,就会开始执行-----如论此刻是HTML解析阶段还是DOMContentLoaded触发之后。需要注意的是,这种方式加载的JavaScript依然会阻塞load时间。换句话说,async-script可能在DOMContentLoaded触发之前或之后执行,但一定在load触发之前执行
③:<script defer src="script.js"></script>
defer属性表示延迟执行引入的JavaScript,即这段JavaScript加载时HTML并未停止解析,这两个过程是并行的。整个document解析完毕且defer-script也加载完成之后(这两件事的顺序无关),会执行所有由defer-script加载的JavaScript代码,饭后触发DOMContentLoaded事件。
defer与普通script相比,有两点区别:载入JavaScript文件时不阻塞HTML的解析,执行阶段被放到HTML标签解析完成之后;在加载多个JS脚本的时候,async是无顺序的加载,而defer是有顺序的加载。
为什么操作DOM慢
因为DOM是属于渲染引擎中的东西,而JS又是JS引擎中的东西,当我们用JS去操作DOM的时候,本质上就是JS引擎和渲染引擎之间进行了“跨界交流”,而这个交流需要依赖桥接接口作为“桥梁”,所以我们每次操作DOM(不管是为了修改还是仅仅为了访问其值),都要过一次“桥”,就会产生比较明显的性能问题。因此现在很多js框架都会减少DOM节点操作也存在这个原因。
说说两个概念,一个是Reflow,另一个是Repaint
重绘:当我们对DOM的修改导致了样式的变化、却并未影响其集合属性(比如修改了颜色或背景)时,浏览器不需要重新计算元素的集合属性、直接为该元素绘制新的样式(跳过了回流环节)
回流: 当我们队DOM的修改引发了DOM几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来,这个过程就是回流(也叫重排)
回流必定会发生重绘,重绘不一定会引发回流
常见引起回流属性的方法:
1、添加或删除可见的DOM元素
2、元素尺寸改变----边距、填充、边框、宽度和高度
3、内容变化,比如用户在input框中输入文字
4、浏览器窗口尺寸改变----resize事件发生时
5、计算offsetWidth和offsetHeight属性
6、设置style属性的值
常见引起重绘属性和方法:
color border-style visibility background text-decoration background-image background-position
background-repeat outline-color outline outline-style border-radius outline-width box-shadow
background-size
如何减少回流、重绘:
1、使用transform替代top
2、使用visibility替换display:none,因为前者只会引起重绘,后者会引发回流(改变了布局)
3、不要把节点的属性值放在一个循环里当成循环里的变量
4、不要使用table布局,可能很小的一个改动会造成整个table的重新布局
5、动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用requestAnimationFrame
6、CSS选择符从右往左匹配查找,避免节点层级过多
7、将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于video标签来说,浏览器会自动将该节点变为图层
性能优化策略:
1、JS优化:<script>标签加上defer属性或async属性,用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。。defer属性:用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。
2、CSS优化:<link>标签的rel属性中的属性值设置为preload能够让你在你的HTML页面中可以指明那些资源是在页面加载完成后即刻需要的,最优的配置加载顺序,提高渲染性能
总结:
1、浏览器工作流程:构建DOM->构建CSSOM->构建渲染树->布局->绘制
2、CSSOM会阻塞渲染,只有当CSSOM构建完毕后才会进入下一个阶段构建渲染树
3、通常情况下DOM和CSSOM是并行构建的,但是当浏览器遇到一个不带defer或async属性的script标签时,DOM构建将暂停,如果此时又恰巧浏览器尚未完成CSSOM的下载和构建,由于JavaScript可以修改CSSOM,所以需要等CSSOM构建完毕后再执行JS,最后才重新DOM构建。