浏览器多进程架构
浏览器是多进程架构:
Browser进程
浏览器的主进程,只有一个。负责以下内容:
浏览器的界面显示,用户交互,前进后退等
负责各个页面的管理,创建和销毁
将渲染进程的结果绘制到界面上
网络资源的管理和下载
第三方插件进程
GPU进程:最多一个,用于3D加速
浏览器渲染进程(浏览器内核,内部多线程)
负责页面渲染、脚本执行、时间处理。
这个进程每个标签页都有一个独立的浏览器渲染进程,所以每添加一个标签页都会新建一个进程,当然不同的空白标签页之间的进程也可以合并起来。
浏览器多进程的优势:
避免单个标签页的崩溃导致整个浏览器的崩溃
避免第三方插件的崩溃导致整个浏览器的崩溃
充分利用多核优势
缺点:因为进程是数据分配的独立单位,所以多个进程也导致了内存的占用更大,像是空间换时间。
渲染进程多线程架构
浏览器渲染进程(内核)是多线程的:
GUI渲染线程
渲染浏览器的界面,解析HTML、CSS,构建dom树和Render树,布局和绘制
重绘、回流
JavaScript引擎线程(js内核)
执行JavaScript
一个标签页只有一个js线程,(js为单线程程序)
js引擎本质上就是函数调用栈(上下文栈)和作用域链的结合
定时器触发线程
隶属于浏览器而不是js引擎,因为js引擎是单线程,没空计时。
根据W3C标准,计时触发的最小时间间隔为4ms
事件触发线程
同样隶属于浏览器,用来控制随事件循环(Event Loop的boss)
当相应事件触发的时候,该线程会将相应的callback加入宏队列的末尾。
异步http请求线程
而其中,GUI渲染线程和JavaScript引擎线程是互斥的,所以script脚本在执行的时候会阻塞DOM树解析渲染,延迟DOMContentLoaded事件触发。
这里就要提到script标签的async和defer属性了,async会异步的下载脚本,但是它的执行依然会阻塞html解析。defer的含义就是将脚本的解析执行推迟到了html解析之后,不会造成HTML阻塞,但是依然会延迟DOMContentLoaded事件触发,其将在defer执行完后触发。多个async-script的执行顺序不能确定,而多个defer-script的执行顺序是确定的。
load、DOMContentLoaded事件的先后
渲染完毕后触发load(绘制阶段)
DOM加载完成后就可以触发DOMContentLoaded(DOM树构建完成,等待js执行完)
webworker、sharedworker
为了解决浏览器js引擎单线程执行时遇到大量计算问题时会产生的卡顿现象,可以通过新建一个隶属于js引擎的线程,专门计算。
sharedworker则是多个渲染线程共享的。
EventLoop
EventLoop本质上就是在宏队列和微队列之间的反复横跳。
宏任务:
setTimeout
setInterval
setImmediate(node)
requestAnimationFrame(浏览器)
I/O
UI rendering(浏览器)
微任务:
Promise
Object.observe
浏览器EventLoop执行过程:
执行全局JavaScript代码,将其中的宏任务压入宏队列,微任务压入微队列。执行过程就是压入函数调用栈和清空函数调用栈。
从微队列中取出一个微任务,压入函数调用栈,清空函数调用栈
再从宏队列取出一个宏任务,压入函数调用栈,清空函数调用栈
重复以上两个步骤。
浏览器渲染流程
主流两种浏览器渲染引擎——webkit(chrome)和Gecko(Firefox)。他们的渲染流程差不多
浏览器渲染的步骤,可以分为以下:
处理HTML标记,构建DOM树
处理CSS标记,构建CSSOM树
将DOM和CSSOM合并成一个渲染树
根据渲染树布局,计算每个节点的几何信息
将各个节点绘制到屏幕上

构建DOM树
浏览器将HTML解析为DOM树,通常发生在初次渲染或者通过js修改DOM时
脚本处理(阻塞)
根据浏览器渲染进程的多线程模式,js引擎线程和渲染线程互为互斥,所以当进行脚本处理时,渲染被阻塞。
构建渲染树
DOM+CSSOM,合并成可视的渲染树。
布局(Layout)
根据渲染树来计算布局,在这个过程中,根据不同的层叠结构分层,不同层次单独布局。
原理:渲染布局的层结构(渲染层、合成层)
渲染树->渲染层->图形层->合成层
渲染对象:一个DOM节点对应一个渲染对象,通过向一个绘图上下文发出绘图调用来引发回流和重绘
渲染层:处于同一个坐标空间(z轴)上的渲染对象将归并到一个渲染层中,不同的坐标空间形成不同的渲染成来表现他们的层叠关系。形成渲染层的触发条件:
根元素document
明确定位属性:relative、absolute、fixed、sticky
opacity<1(透明)
CSS filter属性
CSS mask属性
CSS transform属性
overflow不为visible
满足以上条件的元素(渲染对象)将拥有独立的渲染层,没有独立渲染层的元素将与父元素同用一个渲染层。
合成层:满足特定条件的渲染层会被提升到合成层。合成层拥有独立的图形层。其他不是合成层的渲染层与父级同用一个图形层。合成层的提升条件:
3D transform: transform3d、transformZ
video、canvas、iframe
position:fixed
具有 will-change属性
对:opacity、transform、fliter、backdropfilter 使用了transition和animation
隐式合成:当有普通渲染层元素在合成层上方,为了正确显示其层叠顺序,故将其隐式的并入合成层。
图形层:图形层是负责最终输出图形内容的 层结构,拥有一个独立的图形上下文,图形上下文负责根据合成层生成该层的位图。位图上传到GPU,GPU多个位图合成,呈现在屏幕上。
层结构的优点和缺点
优点:
合成层的位图会传给GPU绘制,速度快很多
当需要repaint重绘时,只对渲染层本身其效果,不会回流到其他层
缺点:
绘制的合成层需要上传GPU,当合成层过多时,会导致传输速率变慢,出现闪烁情况
隐式合成容易产生过量的合成层,占据大量的内容,但在移动端设备上内容十分宝贵。
基于层结构的性能优化
动画尽量使用transform实现,而不是left、top等,这样可以将动画所在节点提升到合成层,GPU加速。否则动画所在节点将与document或拥有独立渲染层父节点置于同一渲染层渲染,不断出现回流。
减少隐式合成,
绘制(paint)
回流必定引起重绘,但重绘不一定引起回流。
遍历渲染树,绘制出节点内容,本质上是一个像素填充的过程。这个过程也出现于回流或一些不影响布局的 CSS 修改引起的屏幕局部重画,这时候它被称为重绘(Repaint)。实际上,绘制过程是在多个层上完成的,这些层我们称为渲染层(RenderLayer)。
重绘(repaint)
元素的样式改变不影响其在文档中的位置,(color、background-color、visibility等),浏览器会重新计算元素的样式。
回流(reflow)
首次渲染
浏览器窗口大小变化
元素的大小、位置变化
元素的直接内容变化(文本、图片等)
添加或删除可见的元素
激活伪类
查询某些属性(需要立刻回流计算当前属性)
浏览器维护一个队列,把所有的回流和重绘操作放入队列中,如果对俄中的任务数量或者事件间隔超过一定阈值,浏览器会立即清空队列。
window.requestAnimationFrame(callback)
下一次重绘之前调用回调函数。
渲染层合成(composite)
多个绘制后的渲染层按照恰当的重叠顺序进行合并,而后生成位图,最终通过显卡展示到屏幕上。其层叠顺序就是来自于层叠上下文。
如何优化
DOM
减少DOM 访问操作
尽量减少使用动态DOM集合(NodeList)因为每次的查询和修改操作都会造成DOM的重新渲染。
合并多次操作
事件
事件委托:把一类元素的事件委托到一个元素上,减少内存中存在的事件监听数量,主要是利用事件冒泡,在父元素中解决多个子元素的事件
CSS
尽量避免table布局
若要通过改变元素的class来改变样式,尽量让其发生在DOM的末端
避免设置多项内联样式
动画应该尽可能被position absolute或fixed包裹
避免过度层叠(CSS选择器),因为选择器的匹配是从右向左的。