(参考文章)
线程与进程的概念
进程是并行的,在同一个时刻,多个进程相互独立执行。进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位。
线程是并发的,表现在在宏观上同步进行,微观上交替执行。在某一时刻,只有一个线程会执行。线程是处理机的调度单位,在多CPU计算机中,各个线程可占用不同的 CPU。由于共享内存空间,同一进程中的线程通信无需系统干预。
较老的浏览器采用的是单进程架构,现代浏览器一般采用多进程架构,现在主流浏览器的多进程架构为:
- 主进程
- 渲染进程
- 插件进程
- GPU进程
- 网络进程
浏览器的线程
GUI渲染线程:渲染浏览器页面,解析HTML,CSS,构建DOM树和Rander树,当触发重绘或回流时,该线程就会执行\
JavaScript引擎线程:执行JavaScript脚本并运行JavaScript代码,无论什么时候都只有一个JavaScript线程在执行JavaScript代码,注意JavaScript线程跟GUI线程都必须作为主线程运行,因此这两个线程是互斥的,过多地用JavaScript操作DOM理论上会出现阻塞,导致渲染不连贯。
事件触发线程:当有事件触发时,会将事件推入一个队列,然后等待JavaScript引擎按顺序执行处理,由于JavaScript是单线程的,因此这些事件会按队列排队执行\
定时器线程:setTimeout和setInterval在该线程中进行处理,由于JavaScript是单线程的,为了防止阻塞对定时器产生的误差,定时器和触发器都会单独采用一个线程进行处理\
http线程:这个线程用于处理网络请求,在XMLHttpRequest连接后出现。这个线程会检测请求中的状态变更。如果设置了回调函数,会将回调函数放到事件线程中等待处理\
浏览器渲染的流程
整个HTML文件从上往下顺序解析,解析到link标签或有外部引入内容的src后会请求外部资源.如果有外部引入的CSS文件,会对其解析并构建CSSOM,解析CSSOM和生成DOM树的过程是可以同步进行的,因此最好将CSS的引入写在HTML文件的上方,让他们同步解析。
在现代浏览器中,一般有一个预处理的机制,会进行第一遍快速扫描,找到需要从外部引入的文件,在解析DOM树之前就开始请求所有外部资源。
由于JavaScript可能会操作DOM,导致已经解析好的DOM被打乱需要重新解析,因此当解析到script标签后,会暂停html的解析,而立即解析JavaScript代码。因此,为了能让我们尽快看到网站的页面,我们应该将script标签写在html的尾部。或者添加属性defer或async,让JavaScript代码等待html解析完再进行解析
处理HTML并构建Dom树\
五个过程:
字节 字符 token node dom\
四个步骤:
- 字符转化:浏览器从磁盘或网络中读取到html的原始字节,根据文件的指定编码(utf-8)将他们转换成各个字符\
- 令牌(token)化: 将解析出的字符串转化为HTML5标准规定的各种令牌,比如各种标签,每个标签拥有特殊的含义和规则。
例如: startTag:html startTag:body startTag div hello endTag div … endTag html - 词法分析:根据token的属性和解析规则,将这些token解析成指定的对象(节点)
- Dom构建:由于html中定义了不同元素的关系(父子关系,兄弟关系等),在这一步,要对这些关系进行构建,确定各个元素节点的关系结构,这部分需要使用建树算法
###处理CSS并构建CSSOM - 格式化:将CSS从0,1的二进制文件解析成浏览器能够识别的styleSheets(样式表)\
- 标准化:在CSS中,有很多相对单位,比如%,em,bolder…对于这些单位,在标准化的过程中会统一转化为标准单位px,对于颜色,red,blue这种写法,和八进制写法会统一转化为rgba表示法\
- 计算:CSS的计算规则:层叠,继承,层叠是指CSS会用一些方法处理从多个地方得到的值,比如相同属性,选择器权重高的覆盖权重低的,后来的覆盖之前的等等。继承是值一些CSS属性是可以被继承的,比如宽度,对于块级元素,如果不给子元素设置宽度,那么子元素会继承父元素的宽度,如果这个宽度不是我们希望看到的,我们可以重写子元素的宽度。。CSS有些属性是可继承的,有些属性是不可继承的。\
将DOM树和CSSOM树合并为渲染(rander)树\
将之前构建的Dom树和CSSOM树合并,这一步中确定了各个节点的基本样式,整个解析路线大致如下:
- 拿到DOM树和样式规则
- filtering:对应该用到页面上的规则用以下条件筛选(包括选择器匹配,属性和状态是否有效,media),查找到每一个元素当前对应属性的样式表,此时样式表中的每个值是声明值
- cascading(层叠):按照来源。!important,选择器特异性,书写顺序选择出优先级最高的一个属性值,这个阶段拿到的值叫做层叠值
- defaulting:当层叠值为空时,使用继承或初始值,这个阶段的值交指定值,这个阶段的值一定不为空
- resolving:对一些相对值或关键字转化为绝对值(比如em转化为px,相对路径转化为绝对路径),这个阶段拿到的值叫做计算值,一般来说,计算值是浏览器在不进行布局时,能拿到的最具体的值。注意,类似于width:60%这类值,计算值是不会转化的,因为如果不进行布局,就无法知道具体宽度。
- formatting:对计算值进行进一步计算,关键字,百分比都会被转化为绝对值,这个步骤得到的使用值,是进行实际布局时使用的值,不会再有相对值或关键字。
- constraining:将有小数点的像素值转化为整数,对超出min-width或max-width的值进行处理,对于浏览器渲染的最小字体进行规范…最后限制后得到实际值
根据渲染树来布局,计算每个节点的位置,生成布局(layout)树
在DOM树中,包含了一些display:none;meta这些不可见的元素,因此浏览器会重新构建一颗仅包含可见元素的树,称为布局树,这一步大体分为一下两部分:
- 遍历DOM树中的所有可见节点,将这些节点加到布局树当中\
- 忽略所有不可见的元素,包括,display:none的元素\
之后,浏览器内核会进行一系列非常复杂的操作,计算各个元素的宽高,确定各个元素在浏览器上显示出来的位置\
在这个过程中,浏览器还会确定各个元素的图层和层级,构建图层树,这部分的详细内容下面会指出.\
GUI绘制
浏览器会根据之前构建的图层树,确定一系列的绘制指令,根据这些构建指令去绘制页面,通常一个元素要用多条指令进行绘制,因为背景,边框都需要专门的指令进行绘制,GUI线程会专门调用一个叫做合成线程的子线程.主线程(此时应为GUI线程)会将绘制指令都提交给合成线程.\
确定了绘制指令后,浏览器会调用操作系统的GPU去执行绘制操作,这里浏览器会将图层划分为图块来解决由于滚动条导致的图层很大,但可视区域不大的问题…谷歌为了保证首屏的加载速度,还采用了第一次采用低分辨率图片,之后正常图块绘制完毕后,再将低分辨率图块替换的策略.\
位图,亦称为点阵图像或栅格图像,是由称作像素(图片元素)的单个点组成的
有了图块后,合成线程会按照视口附近的图块来优先生成位图(栅格化),渲染进程中专门维护一个栅格化线程池,负责把图块转换成位图数据,合成线程会选择视口附近的图块送往栅格化线程池,将他们生成位图.生成位图的过程中会依靠GPU进行加速,生成的位图最后会发送给合成线程\
栅格化完成后,合成线程会生成一个绘制命令,并发送给浏览器进程.浏览器进程中的viz组件收到这个命令后,根据这个命令将页面绘制到内存中,然后将这部分页面发送到显卡.也就能够显示出页面了.显卡分为前缓冲区和后缓冲区,这两部分不断地相互交换,使屏幕的内容不断变化.形成了动画效果.
重绘与回流,图层问题
重绘:
页面元素的样式改变,但并不影响它在文档流中的位置,浏览器会将新样式赋给他并重新绘制.更改color,visibility属性会触发重绘\
###回流(重排):
对DOM结构,样式的修改引发了DOM几何尺寸,位置的改变,会触发回流,回流会重新绘制DOM树,重新执行之后的全部流程触发回流的情况包括:
- DOM元素的几何属性变化,包括width,padding,left等
- DOM元素的增减,移动
- 读写offset,scoll等属性
- 调用window.getComputedstyle方法
重绘的成本远低于回流,因此我们尽可能少地触发回流.比如:\
- 当要隐藏元素时,用修改visiblity的方法来代替修改display.
- 当要修改一个父元素中的大量子元素时,可以进行离线处理,先将父元素的display设为none,再处理子元素,这样仅触发两次回流.
- 尽量不要将offset,scoll这些属性直接放在循环内部进行处理,因为每次读取都会触发回流
合成
当对于一个既不需要布局也不需要绘制的属性,渲染流程会跳过布局和绘制的操作,直接执行后面的合成操作,比如CSS中的transfrom实现动画,就避免了重绘和回流,直接在非主线程上执行合成的操作,大大提高效率.同时合成的位图会由GPU进行处理,比CPU处理的效率要高.\
可以进行合成的CSS属性:
- transfrom
- opacity
- filter
图层:
一些属性可以让元素称为一个单独的图层,图层能够阻止该节点的回流影响其他元素,
能够产生图层的方法有:
- 设置了position属性并且设置了z-index
- opacity的值不为1
- transfrom的值不为none
- filter的值不为none
- 设置will-change属性
- 使用video标签的节点
- 出现了剪裁或滚动条的情况
注意:当z-index有比较低的数值但提升为一个独立的图层,那么z-index比他高的节点都会被提升为一个独立的图层,这时可能出现层爆炸的情况,浪费大量内存