浏览器多进程
浏览器包含的进程:
- Browser主进程
- 第三方插件进程
- GPU进程
- 浏览器渲染进程(浏览器内核,每个Tab页面一个进程,是多线程)
浏览器渲染进程的GUI渲染线程,最后将渲染树绘制到页面上,然后浏览器会将各层的信息发送给GPU进程,GPU会将各层合成,显示在屏幕上。
普通图层和复合图层:
浏览器渲染的图层一般包含普通图层以及复合图层。GPU中,各个复合图层单独绘制,互不影响。
普通文档流可以理解为一个复合图层(默认复合层),absolute布局等虽然可以脱离普通文档流,但它仍然属于默认复合层;
可以通过硬件加速的方式,声明一个新的复合图层,它会单独分配资源。新复合图层中的变化,不会影响默认复合层里的回流重绘。
浏览器渲染进程(浏览器内核)
包含:
- GUI渲染线程
- JS引擎线程
- 事件触发线程
- 定时器触发线程
- 异步http请求线程
GUI渲染线程
负责渲染浏览器界面,重排重绘。
GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。
渲染完毕后会触发load事件:
DOMContentLoaded(仅当DOM加载完成,不包括样式表,图片) -> load(页面上所有的DOM,样式表,脚本,图片都已加载完成)
css是由单独的下载线程异步下载的,css加载不会阻塞DOM树解析(异步加载时DOM照常构建)
但会阻塞render树渲染(渲染时需等css加载完毕,因为render树需要css信息)
渲染引擎的分层和合成机制
当滚动页面或者手势缩放页面时,屏幕上就会产生动画的效果。是因为在滚动或者缩放操作时,渲染引擎会通过渲染流水线生成新的图片(帧率=渲染流水线每秒更新的帧数),并发送到显卡的后缓冲区。然后系统会让后缓冲区和前缓冲区互换,保证显示器在前缓冲区中读取到最新的图像(刷新率)。
渲染引擎是如何生成一帧图像:有重排、重绘和合成三种方式
合成操作不需要触发布局和绘制两个阶段,比重排和重绘效率高。Chrome 中的合成技术包括分层、分块和合成。
1、分层和合成机制
生成布局树之后,渲染引擎会根据布局树的特点将其转换为层树(Layer Tree),层树中的每个节点都对应着一个图层,下一步的绘制阶段就依赖于层树中的节点。
绘制出每一个图层的图片后,合成线程将这些图片合成为“一张”图片,并最终将生成的图片发送到后缓冲区。这就是一个大致的分层、合成流程。
(在合成线程中实现的是整个图层的几何变换,透明度变换,阴影等,不会影响到图层的内容。)
合成操作是在合成线程上完成的,不会影响到主线程执行。
2、分块
显示一个页面时,如果等待所有的图层都生成完毕,再进行合成的话,会耗费更长时间。因此,合成线程会将每个图层分割为大小固定的图块,然后优先绘制靠近视口的图块,可以加快页面的显示速度。
将图层从计算机内存上传到 GPU 内存的操作会比较慢,Chrome又采取了一个策略:在首次合成图块的时候使用一个低分辨率的图片。
在首次显示页面内容的时候,将这个低分辨率的图片显示出来,然后合成器继续绘制正常比例网页内容;当正常比例的网页内容绘制完成后,再替换掉当前显示的低分辨率内容。这种方式尽管会让用户在开始时看到的是低分辨率的内容,但是也比什么都看不到要好。
代码优化:
使用 will-change 属性来告诉渲染引擎会对某个元素做一些特效变换,渲染引擎会将该元素单独实现一层。当变换发生时,渲染引擎会通过合成线程直接去处理变换,就没有涉及到主线程。这样就提升了渲染的效率,这也是 CSS 动画比 JavaScript 动画高效的原因。
(使用js改变transform也能享受这个属性带来的优化。既然css动画和js动画都能享受这个优化,那就不能说明css动画比js动画效率高)
但当渲染引擎为一个元素准备一个独立层,它占用的内存也会大大增加,因为从层树开始,后续每个阶段都会多一个层结构,这些都需要额外的内存,所以要恰当地使用 will-change。
JS引擎线程 、事件触发线程...
JS引擎是单线程的,而且JS执行时间过长会阻塞页面,所以HTML5中支持了Web Worker。
Web Worker: (WebWorker与SharedWorker)
为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。主线程与 Worker 线程的运行互不干扰,有利于 Worker 线程随时响应主线程的通信。等到 Worker 线程完成计算任务,再把结果返回给主线程。
JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)
特点:
Worker 线程运行的脚本文件,必须与主线程的脚本文件同源;
Worker 线程无法读取主线程所在网页的 DOM 对象,也无法使用document
、window
、parent
这些对象,但是可以navigator
对象和location
对象。
优缺点:Worker 线程可以负担一些计算密集型或高延迟的任务,主线程(通常负责 UI 交互)就不会被阻塞或拖慢。但是,Worker 比较耗费资源,不应该过度使用,一旦使用完毕就应该关闭。
- WebWorker只属于某个页面,每一个render进程(Tab页)创建一个新线程来运行Worker中的JavaScript程序。
- SharedWorker是浏览器所有页面共享的,Chrome浏览器为SharedWorker单独创建一个进程来运行JavaScript程序。
事件触发、定时器触发、异步http请求线程的回调任务会添加到任务队列中,等待JS引擎的处理。
(事件循环)
JS引擎线程是主线程,形成一个执行栈,执行同步任务;
事件触发线程管理一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
当使用setTimeout或setInterval时,需要定时器线程计时,计时完成后就会将特定的事件(回调函数)推入任务队列中。
在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)
macrotask:主代码块script、setTimeout\setInterval等(事件队列中的每个事件都是macrotask)
microtask:Promise.then、process.nextTick等(node下process.nextTick的优先级高于Promise)
(Promise
的官方版本与polyfill
的区别:官方版本中是标准的microtask形式;polyfill一般都是通过setTimeout模拟的,所以是macrotask形式)
总结运行机制:
- 执行一个宏任务(执行栈中没有就从任务队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即依次执行当前微任务队列中的所有微任务
- 当前宏任务执行完毕后检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管执行下一个宏任务(从任务队列中获取)