前言
为能更好的理解浏览器性能优化,本文会从浏览器多进程架构以及浏览器渲染过程逐步简单解析性能优化要点
浏览器的多进程架构
进程与线程
进程类似于一个工厂,工厂拥有独立的资源且相互独立
线程类似于工厂中的工人,多个工人协作完成任务且相互共享空间
简单理解:
进程是cpu资源分配的最小单位,系统会为其分配内存
线程是cpu调度的最小单位,一个进程中可拥有一个或多个线程
不同进程之间也可以进行通信但是代价较大
浏览器多进程
简单理解:
浏览器是多进程的
浏览器之所以能够运行是因为系统为其进程分配了cpu于内存资源
基本上每打开一个Tab页就相当于创建了一个独立的浏览器进程
注:浏览器有自身的优化机制,打开多个tab页后部分tab页的进程会被合并(例如多个空白页合并)
为什么浏览器是多进程的(主要原因)
想象如果浏览器是单线程的,某个Tab页或是插件脚本崩溃了就会影响到整个浏览器,所以浏览器为保证稳定性设计为多进程
浏览器包含哪些主要进程
1. Browser进程:浏览器的主控进程,只有一个
负责浏览器界面显示,与用户交互
负责各个页面的管理,创建或销毁其他进程
将浏览器渲染进程存放在内存中的bitmap(位图)绘制到用户界面上
网络资源的管理,下载等
2. 第三方插件进程:每种类型的插件对应一个进程,使用插件时才创建
3. GPU进程:最多存在一个,用于3D绘制
4. 浏览器渲染进程(Renderer进程):该进程内部为多线程,默认每个Tab页面一个进程,互不影响
页面渲染,脚本执行,事件处理等
细化浏览器渲染进程
浏览器中页面的渲染,js的执行,事件循环等都在该进程中执行,其中包含多个线程
1. GUI渲染线程
该线程负责渲染页面,解析HTML于CSS,并构建DOM树并生成RenderObject树
单页面需要重绘或是因为某些操作回流时,该线程就会执行
注:为保证不会因为渲染时改变DOM导致的页面错乱,GUI渲染线程与JS线程是互斥的
2. JS引擎线程
其为JS内核,负责处理JS脚本语言(V8引擎)
一个Tab页(renderer进程)中无论什么时候都只有一个JS线程在运行JS程序
3. 事件触发线程
其归属于浏览器用于处理被触发的事件并将其回调函数放置于任务队列中,待JS主执行栈空时按一定规则执行任务队列中的回调函数
4. 定时器触发线程
用于处理setTimeout或是setInterval
5. 异步HTTP请求线程
用于处理页面中存在的异步HTTP请求,并将回调放入事件队列中,最后由js引擎执行
页面构建过程
浏览器处理部分
开启进程存放页面
进程中包含
GUI渲染线程:用于获取到HTML后进行页面的构建与渲染
JS引擎线程:用于处理页面中的JS代码,每个浏览器进程都有一个
事件处理线程:用于处理用户触发事件产生的回调函数
HTTP处理线程:用于处理HTTP请求
定时器触发线程:用于处理页面中生成的定时器,并在合适的时候将回调交由任务队列
注:为保证数据与视图的统一性,GUI线程与JS线程存在互斥关系
构建流程(GUI渲染线程)
1. 浏览器开始逐行解析HTML并开始创建DOM树,DOM树的创建是一个深度遍历的过程,当前节点的所有子节点构建完成后才会构建当前节点的下一个兄弟节点,此时的document.readyState="loading"
2. 如果遇到外联JS文件且没有设置异步执行则立即下载并同步执行,遇到CSS文件则立即下载并生成CSSOM
3. HTML解析完成后将完整DOM树与CSSOM结合生成渲染树RenderTree,此时的document.readyState="interaction"
渲染流程
1. RenderTree是由与DOM节点一一对应的渲染对象RenderObject组成的,其包含了节点信息以及渲染上下文
2. 处于相同坐标空间的渲染对象都会归并到一个渲染层中,对于形成层叠上下文的渲染对象会自动为其创建新的渲染层,渲染对象自动从属于父元素最近的渲染层,常见生成渲染层的方式
根元素document
postion: relative/absolute/fixed/sticky
opacity < 1
存在CSS fliter属性
存在CSS transform属性
3. 在渲染层的基础上满足特定条件后该渲染层会被提升为合成层(图形层),拥有单独的图形上下文(相当于进行单独渲染),其余不是合成层的渲染层和第一个拥有图形上下文的父层共用一个合成层,合成层通常为经常变动的效果
3Dtransforms: translate3D/translateZ
video/canvas/iframe
CSS动画实现的opacity动画转换
position:fixed
will-change
对opacity/transform/fliter应用了transition或animation
使用了裁剪(Clip)或者反射(Reflection)
注:将渲染层提升为合成层会带有GPU加速效果,但是不能滥用
4. 得到各层后浏览器会计算出各层在页面中的位置(回流)并调用其绘图上下文进行绘制渲染,最终呈现出整个页面
隐式合成与层爆炸、层压缩
若一个或多个非合成元素堆叠在合成层元素上,这些非合成层元素就会被提升成合成层,从而出现隐式合成
层爆炸指产生的合成层太多导致大量占用GPU与内存资源,进而导致页面卡顿或闪烁的现象;其解决方法是提高合成层的z-index从而避免层叠来消除隐式合成
层压缩是浏览器自身优化机制,会将隐式合成产生的多余堆叠合成层压缩为一个合成层,从而大大减少合成层的数量,降低GPU与内存消耗
层合成规则的优缺点
优点
层合成产生的位图会交由GPU进行单独处理,其处理速度比CPU快
需要重绘是只需重绘其自身不影响其他层
元素提升为合成层后transform或是opacity的改变部触发重绘
缺点
合成层过多导致GPU占用过高,容易出现页面闪烁
隐式合成产生过多的合成层,会占用过多的资源
优化方式
动画使用transform实现
减少隐式合成,动画节点设置高z-index
减小合成层的尺寸,可以使用scale放大,节省性能
重绘与回流
重绘:尺寸与布局不发生改变,仅改变部分样式
回流:页面中元素的尺寸与布局发生变化(合成层除外)
重绘不一定回流,回流一定重绘
引起回流的操作
页面初始渲染
改变字体或元素的尺寸
改变元素的可见内容
增删DOM元素
fixed的元素滚动时会一直回流
调整窗口大小
访问offsetWidth或是offsetHeight等破坏flush队列的操作
flush队列
为性能优化考虑,浏览器自身会维护一个flush队列,该队列存储着回流的内容,每经过一段时间或该队列达到一定长度后浏览器就会一次性将其全部回流完成,实现优化性能的目的。但当开发者访问offsetwidth,offsetheight,width,height等属性时,浏览器为了给开发者返回该元素准确的值会破坏flush队列,致使浏览器自身优化失效
页面优化性能方法
代码层面
元素位置变换时尽量使用css3的transfrom来代替top left的操作,因为变换仅仅影响图层的组合位置
使用opacity来代替visibility,因为透明度并不触发重绘
注:透明度改变时GPU在绘画时只是简单的降低之前画好纹理的alpha值来达到效果
将多次class样式改变操作合并为一次(预先定义改变后的class)
使用display:none将dom元素离线后修改
利用文档碎片documentFragment收集短时间内回流元素后一次回流添加到页面(vue同款优化)
动画实现过程中使用transfrom:tranleteZ(0)使其提升为合成层通过GPU单独渲染
为动画元素新建图层并提高其z-index以减少隐式合成
减少对flush队列的访问,尽量不要破坏浏览器自身优化
单调色块可使用合成层优化中的scale缩放来优化内存占用
CSS在上JS在下,触发浏览器的first paint且减少同步代码阻塞渲染的机率
资源层面
除首屏展示外其余资源(图片、组件、视频等)懒加载
首屏中包含的视频可以进行分割传输
单调图片过大导致性能问题时可使用canvas或是svg绘图
对HTML、JS、CSS资源进行压缩,服务器开启Gzip
对静态资源使用缓存
使用CDN缩短用户与资源的距离 * 升级到HTTP2使资源可以享受二进制传输、首部压缩、多路复用