一、概述
渲染进程与我们前端日常开发生活息息相关,需要着重了解。可以这样理解,页面的渲染,JS的执行,事件的循环,都在这个进程内进行。
二、渲染进程包含哪些线程呢?
- GUI渲染线程
- 当浏览器收到响应的html后,该线程开始解析HTML文档构建DOM树,解析CSS文件构建CSSOM,合并构成渲染树,并计算布局样式,绘制在页面上(该处可深挖的坑,HTML解析规则,CSS解析规则,渲染流程细节)
- 当界面样式被修改的时候可能会触发reflow和repaint,该线程就会重新计算,重新绘制,是前端开发需要着重优化的点
- JS引擎线程
- JS内核,也称JS引擎(例如V8引擎),负责处理执行javascript脚本程序,
- 由于js是单线程(一个Tab页内中无论什么时候都只有一个JS线程在运行JS程序),依靠任务队列来进行js代码的执行,所以js引擎会一直等待着任务队列中任务的到来,然后加以处理。
- 事件触发线程
- 归属于渲染(浏览器内核)进程,不受JS引擎线程控制。主要用于控制事件(例如鼠标,键盘等事件),当该事件被触发时候,事件触发线程就会把该事件的处理函数添加进任务队列中,等待JS引擎线程空闲后执行
- 归属于渲染(浏览器内核)进程,不受JS引擎线程控制。主要用于控制事件(例如鼠标,键盘等事件),当该事件被触发时候,事件触发线程就会把该事件的处理函数添加进任务队列中,等待JS引擎线程空闲后执行
- 定时器出发线程
- 传说中的setInterval与setTimeout所在线程
- 浏览器的定时器并不是由JavaScript引擎计数的,因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响计时的准确,因此通过单独的线程来计时并触发定时器,计时完毕后,满足定时器的触发条件,则将定时器的处理函数添加进任务队列中,等待JS引擎线程空闲后执行。
- W3C在HTML标准中规定,规定要求setTimeout中低于4ms的时间间隔算为4ms
- 异步HTTP请求线程
- 当XMLHttpRequest连接后,浏览器会新开的一个线程,当监控到readyState状态变更时,如果设置了该状态的回调函数,则将该状态的处理函数推进任务队列中,等待JS引擎线程空闲后执行
注意:浏览器对通一域名请求的并发连接数是有限制的,Chrome和Firefox限制数为6个,ie8则为10个。
- 当XMLHttpRequest连接后,浏览器会新开的一个线程,当监控到readyState状态变更时,如果设置了该状态的回调函数,则将该状态的处理函数推进任务队列中,等待JS引擎线程空闲后执行
总结:2-5 四个线程参与了JS的执行,但是永远只有JS引擎线程在执行JS脚本程序,其他三个线程只负责将满足触发条件的处理函数推进任务队列,等待JS引擎线程执行。
三、GUI渲染线程与JS引擎线程互斥
由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。
因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系。
当JS引擎执行时GUI线程会被挂起,GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。
四、JS阻塞页面加载
从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。
譬如,假设JS引擎正在进行巨量的计算,此时就算GUI有更新,也会被保存到队列中,等待JS引擎空闲后执行。然后,由于巨量计算,所以JS引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比。
所以,要尽量避免JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
五、CSS加载是否会阻塞DOM树渲染?
- css加载不会阻塞DOM树解析(css是由单独的下载线程异步下载的。异步加载时DOM照常构建)
- 但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)
为什么会阻塞render树的渲染?
这可能也是浏览器的一种优化机制。
因为你加载css的时候,可能会修改下面DOM节点的样式,如果css加载不阻塞render树渲染的话,那么当css加载完之后,render树可能又得重新重绘或者回流了,这就造成了一些没有必要的损耗。
所以干脆就先把DOM树的结构先解析完,把可以做的工作做完,然后等你css加载完之后,在根据最终的样式来渲染render树,这种做法性能方面确实会比较好一点。
六、浏览器的渲染过程
- 概念
DOM Tree
: 浏览器将HTML
解析成树形的数据结构。CSS Rule Tree
:浏览器将CSS
解析成树形的数据结构。Render Tree
:DOM
树和CSS
规则树合并后生产Render
树。Layout
(布局):有了Render Tree
,浏览器已经能知道网页中有哪些节点、各个节点的CSS
定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。Painting
(绘制): 按照算出来的规则,通过显卡,把内容画到屏幕上。Reflow
(回流):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫reflow
。Repaint
(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。- 注意:
display:none
的节点不会被加入Render Tree
,而visibility: hidden
则会,所以display:none会
触发reflow
,visibility: hidden
会触发repaint
。
- 浏览器内核(渲染进程)拿到响应报文之后,渲染大概分为以下步骤:
- 解析
HTML
生成DOM Tree
,同时浏览器主进程负责下载CSS
文件 CSS
文件下载完成,解析CSS
生成CSS Tree
。- 根据
DOM Tree
和CSS Tree
生成Render Tree
。 - 根据
Render
树进行Layout
,负责各个元素节点的尺寸、位置计算。 - 绘制
Render
树(Painting
),绘制页面像素信息。 - 浏览器主进程将
默认图层
和复合图层
交给GPU
进程,GPU
进程再将各个图层合成(composite
),最后显示出页面
- 解析
-
渲染完毕后
JS
引擎开始执行load
事件,绘制流程见下图:
-
load
事件与DOMContentLoaded
事件的先后-
当
DOMContentLoaded
事件触发时,仅当DOM
加载完成,不包括样式表,图片。(譬如如果有async加载的脚本就不一定完成) -
当
onload
事件触发时,页面上所有的DOM
,样式表,脚本,图片都已经加载完成了。(渲染完毕了)
所以,顺序是:DOMContentLoaded
->load
-
七、普通图层和复合图层
渲染步骤中就提到了composite
概念。
可以简单的这样理解,浏览器渲染的图层一般包含两大类:普通图层
以及复合图层
首先,普通文档流内可以理解为一个复合图层(这里称为默认复合层
,里面不管添加多少元素,其实都是在同一个复合图层中)
其次,absolute
布局(fixed
也一样),虽然可以脱离普通文档流,但它仍然属于默认复合层。
然后,可以通过硬件加速
的方式,声明一个新的复合图层
,它会单独分配资源
(当然也会脱离普通文档流,这样一来,不管这个复合图层中怎么变化,也不会影响默认复合层
里的回流重绘)
可以简单理解下:GPU
中,各个复合图层是单独绘制的,所以互不影响,这也是为什么某些场景硬件加速效果一级棒
可以Chrome
源码调试 -> More Tools
-> Rendering
-> Layer borders
中看到,黄色的就是复合图层信息
- 复合图层的好处
- 合成层的位图,会交由
GPU
合成,比CPU
处理要快 - 当需要
repaint
时,只需要repaint
本身,不会影响到其他的层 - 对于
transform
和opacity
效果,不会触发layout
和paint
- 但是尽量不要大量使用复合图层,否则由于资源消耗过度,页面反而会变的更卡
- 合成层的位图,会交由
- 如何变成复合图层(硬件加速)
- 最常用的方式:
translate3d
、translateZ
(3D
或透视变换) <video><iframe><canvas><webgl>
等元素- 元素有一个
z-index
较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染) - 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
- 最常用的方式:
- 硬件加速时请使用
index
- 使用
3D
硬件加速提升动画性能时,最好给元素增加一个z-index
属性,人为干扰合成层的排序,可以有效减少chrome
创建不必要的合成层,防止层爆炸,提升渲染性能,移动端优化效果尤为明显。 - 原理:如果这个元素添加了硬件加速,并且
index
层级比较低,那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative
或absolute
属性相同的),会默认变为复合层渲染
,如果处理不当会极大的影响性能 - 简单理解:如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层
- 问题验证:http://web.jobbole.com/83575/
- 使用