本文通过浏览器关键路径渲染、内存泄漏和资源优化三部分来介绍自定义组件性能优化的方法。
关键渲染路径
浏览器关键渲染路径主要包括五部分:JavaScript、Style、Layout、Paint和Composite。
JavaScript
对于JavaScript的优化,一般从缩减代码量、限制代码执行频率、加速代码执行速度以及加速代码解析编译时间四部分来完成。
缩减代码量:
方法一:删除未使用的代码和功能。
方法二:移除上下文中未引用的代码(Tree shaking技术)。
说明Tree shaking技术用来检测代码模块是否被导入导出。
方法三:合并压缩。
限制代码执行频率:一般使用Throttle(节流)和Debounce(防抖)技术控制函数执行频率。其中节流是指一个函数在某个时间内, 被连续调用,让其间隔一段时间执行一次;防抖是指一个函数在某个时间内,无论触发多少次,都只执行最后一次。
加速代码执行速度:由于基于代码行为的优化细节比较多,只罗列部分进行讲解。
合理使用缓存机制:代码对实时性要求不严格,经常访问但改动不频繁的内容。
循环优化:避免使用for…in循环,尽可能的减少迭代次数和每次迭代的工作量,使用return尽早退出多重循环。
当条件语句特别多时:使用的优先级为,查找表 >
switch
>if-else
加速代码解析编译速度-函数优化:函数在定义期间,只会被“预解析”,只有被调用时才会进行“解析”编译。如果函数需要被立即执行,那么对其进行“预解析”就是资源浪费。因此,可以采取以下方法对函数进行优化:
避免函数的预解析,将函数用括号括起来并赋值给一个变量,在解析脚本时,V8就不会对sayHi函数进行预解析,而是将整个脚本都完全解析并编译成本地代码。
尽可能确保函数入参的类型和数量一致,这样编译器会提高当前函数的执行效率,否则会导致不优化或者优化回滚。
说明参数的类型对编译器的优化机制影响非常大。
加速代码解析编译速度-对象优化:V8中一个重要的优化机制就是内嵌缓存(inline cache),大致思路就是将之前查找的隐藏类和偏移值保存起来,当下次查找的时候,先比较当前对象是否是之前的隐藏类,如果是直接使用之前的缓存结果,减少查询时间。
避免创建一个宽泛的元素类型,一旦被标记为宽泛的元素类型,在生命周期内将不能更改为精度更高的类型。该过程不可逆,如果需要更改元素类型,只能重新创建元素。
避免使用类数组,如果要用,可以转换为数组后再操作,转换后的性能会比直接使用类数组高 。
避免写出读取超过数组长度的代码即越界,因为在这种情况下,V8的边界检查失败,属性检查也失败,会依次向上查找原型链,降低编译速度。
避免元素类型转换。
Style
关于样式计算的样式通用优化可以通过以下几个方面来完成:
尽可能的样式共享,如果能共享,就不需要执行匹配算法,执行效率将会提高 。
减少昂贵属性开销,如box-shadow、filter、:nth-child、border-radius等属性的使用。
使用更先进的CSS属性,如: Chromium85 版本中新增的content-visibility属性。添加这个新属性后,具体元素会直接跳过元素的渲染工作,包括布局和绘制,这样会使初始页面加载更快。
测试案例:创建一个185张图片的测试案例,做content-visibility相比Rendering的数据测试。分别各测试10次,去掉最大最小值,取平均值,得到的结果是添加了content-visibility比不添加Rendering页面加载率提升31.42%,即初始化的渲染性能提升了31.42%左右,其他同行技术文章中的测试结果,有的提升多达7倍之多。这个结果反映了该方案是一个可以使初始用户加载速度更快的CSS方案。
在CSS解析计算阶段,对现代浏览器来说,代码层面带来的产出比并不高,大部分是开发规范层面的约定,在增强可维护性的基础上,尽可能的贴合解析器的解析流程。因此需要重点关注CSS导致的渲染性能问题。
Layout
布局:布局上的性能优化,可以从以下几个方面进行。
尽可能减少不必要的元素,布局作用于整个文档,如果有大量元素,浏览器将需要很长时间来算出所有元素的位置和尺寸。
布局时,尽可能的使用Flexbox,和浮动布局相比,在相同的元素数量下,Flexbox将有4到5倍的性能提升,并且Flexbox非常容易使用。
删除元素默认属性,默认属性会无故增加文件大小,同时需要减少iframes的使用以及避免table布局
布局-重排:在真实环境中,会不可避免的触发重新布局场景,即重排,会对性能有很大的损耗,因此可以尽可能减少重排,来降低性能损耗。对于重排,只要修改当前元素的几何信息,如位置、大小和显隐等,就会触发重排。可以通过以下几个方面来减少重排。
避免强制同步布局和布局抖动,需要做到读写操作分离。如果读写不分离对性能来说将是灾难性的,DOM操作上可以使用FastDOM库去优化。
避免频繁的样式修改,尽量集中修改样式。
使用absolute、fixed脱离文档流,减少重排开销。
将DOM离线:使用
diaplay:none;
隐藏样式后,进行修改然后再显示,这样仅触发两次重排。
布局-读写操作分离:将布局抖动和读写分离两种方式进行对比如下。
布局抖动(读写循环)
读写分离
Paint(绘制)
绘制中的性能优化可以通过减少昂贵属性的使用来实现,包括box-shadow、filter、border-radius、:nth-child等属性。例如在同一个层中,如果某个组件DOM元素较多,比如滚动长列表。当在滚动列表上添加box-shadow属性,列表滚动时,会使和其交叉的其它DOM元素连带触发重绘,相连节点越多性能开销将会越大。
Composite(渲染层合并)
渲染层合并的性能优化可以通过以下方法来实现。
动画使用transform实现:使transform相关元素放置到一个独立的合成层中进行渲染绘制,并且动画不会影响到其它层。
减少隐式合成即消除无意义合成层:对于隐式合成会造成不必要的开销,如AB两个元素,都添加定位属性,此时元素A开启GPU渲染,元素B保持不变,此时浏览器为了保证正确的图层堆叠顺序,会把B元素提升为单独的合成层。可以通过约束自己的布局习惯来解决,如某个元素单独开启GPU渲染时,尽可能使当前元素z-index大于其它值,尽可能保证当前元素在文档中的先后顺序,以此来消除无意义的合成层。
减少合成尺寸,使用scale进行放大处理来节省内存使用。如对于一下两种元素:A元素width: 100pxheight: 100px;内存占用40KB;B元素width: 10px; height: 10px;内存占用400B在合成层中占用的内存,差距是很大的,B可以进行放大处理
transform: scale(10);
内存泄漏
在内存泄漏方面,可以通过以下方面优化性能。
一个页面周期结束后,通过removeEventListener销毁页面上所有的事件监听,例如:click、visibilityChange、Promises、Observables及EventEmitters等。
使用定时器setTimeout或setInterval时,需要使用clearTimeout或clearInterval清除它们。
清理DOM元素的引用。
尽量避免使用全局变量。
在任务结束后,清除闭包内的局部变量。
说明您可以使用以下方法判断内存泄漏。
使用Chrome调试(Performance、Performance monitor、Heapsnapshot)。
使用queryObjects去判断页面所有object长度,判断是否存在内存泄漏。
资源优化
加快资源链接
加快资源链接,可以通过以下方面优化性能。
内容分发网络(CDN):可以拉近内容与用户之间的绝对距离。
DNS预加载:可以加快DNS解析速度。
多域名加速访问(domain hash ) :优化因低版本HTTP并发限制,导致的加载资源阻塞。
强缓存:静态资源强制使用本地缓存,其带来的复杂性只有在资源更新时,发布不同的UR。
减小资源大小
减小资源大小,可以通过以下方面优化性能。
html/css/js:将废代码(未使用、未引用的html或css及有默认值的)去除、合并或压缩。
图片:可以将图片合并为webp精灵图,或使用iconfont减少图片大小。
视频链接
DataV小课堂直播视频:自定义组件-性能优化直播视频使用教程。