淘宝新势力周H5性能优化实战
前言
淘宝新势力周(春上新)是命运石kimi链路(H5链路)第一次承接S级大促,面对S级大促内容丰富且复杂的页面需求,kimi链路遇到了一些性能问题,在未进行性能优化之前,搭建出来的页面,业务方普遍反馈页面卡顿严重,无法滑动。
因为时髦女装会场是反馈比较严重的页面之一,所以我以时髦女装会场为例子,介绍下这次性能优化的具体方案。时髦女装会场页面模块在18个左右,页面上的img标签数量在200左右,页面总长度 31208px,以iPhone6页面一屏736px为标准,总共能分为42.4屏左右。为什么我要特别把img标签写出来呢?因为这次的性能卡顿主要的原因是因为错误使用图片懒加载引起的。
通过performance图排查性能问题
现代的web性能优化离不开chrome devtool里performance的帮助,我们先来看一张未优化之前 performance的截图
这张performance图我们主要看三个部分:第一个是最上面FPS红线的部分,红线代表着这段时间内未达到60FPS每帧;第二部分是Frames的耗时,勾选了Screenshots后我们能看到每帧的耗时;第三部分是下面函数耗时,我们能从函数耗时里分析出来到底是哪段代码block住了页面渲染,导致卡帧。
从上面的图可以看到最长的一帧耗时3.37秒,这导致FPS都快接近0了。
把函数耗时图拉大分析里面耗时最长的函数,可以看到耗时最长的函数是inview函数,这个函数是图片懒加载库里面检查当前图片是否在屏幕中间的函数。
图片懒加载库的基本逻辑是:当调用初始化函数时立即检查当前页面上所有未真正加载的图片,判断是否需要加载。当页面进行滑动时,重复检查所有图片的逻辑。
这次性能问题的原因和解决方案
卡顿掉帧的原因:这次搭建出来的页面使用的是外包同学开发的业务模块,在模块内部手动调用了lazyLoad初始函数,所以每初始化一个模块就会立即检查所有未加载图片,当页面上图片数量不断增长的时候,inview函数的耗时也不断增加,检查一个图片是否在页面的耗时是2ms~5ms,如果页面中有100个图片未加载当页面滑动时每一次检查会耗时200ms~500ms,如果检查是同步操作的话,掉帧几乎无法避免。
优化方案:之前的其他链路的优化方案是模块懒加载,然后lazyload统一调用,但是因为这次离上线时间较紧张,让外包返工改模块风险较大,于是有另外的一个优化方案:图片懒加载库的异步化,只要避免函数执行耗时过长阻塞渲染,就能避免卡帧,假设我们有100张图片,我们分多批次进行检查,避免一次检查所有图片阻塞渲染。另外针对模块初始化时频繁的检查所有图片的问题,我们给这段逻辑加上debounce函数和图片缓存队列。
优化的过程
优化1.0:
在我接手之前,有一版优化是将模块的渲染通过setTimeout函数改成异步的;这个优化是几乎没有效果的,优化后页面依然卡顿掉帧,因为这个优化并没有找到页面卡顿的原因。起码也应该将setTimeout改成RAF。当然模块的延时加载并不能解决卡顿问题,但是模块的懒加载能解决一部分问题。下面我们看一张使用模块懒加载后的performance图
模块懒加载后,一长条红色块已经变成了短条的红色块,但是因为模块内部单独使用图片懒加载导致频繁检查所有图片是否在可视范围内的问题还是没有得到解决,最长的一帧达到855ms,依然存在掉帧。
优化2.0:
图片懒加载异步版本:通过对图片懒加载库的改造,1、初始化时加上debound优化和图片缓存队列,2、分批检查图片。我们在看一下优化后的performance图
红色的条块也消失,看下面函数执行变的又长有尖,这是因为检查图片的操作变成异步分批了。
图片懒加载库改造时遇到的问题:
在将图片懒加载改造成异步的时候遇到了一个问题,就和Java多线程一样,很多时候异步我们也希望是有序的异步。
分批检查的有序是比较容易保证的,将图片分成多批,一批一批进行,再最后一批结束任务。但是问题出在分批检查和图片懒加载模块初始化存在交替运行的情况,而这两个任务都会改变一个变量。如果不能解决这个问题,就会出现图片有时候能正常加载,有时候加载不出来的情况。所以有说法是,大部分偶现的问题都是异步并行的问题。
解决这个问题的思路也比较常见,就是通过锁,当一个操作异步变量的任务开启,我们的锁自增1,完成异步任务时自减,图片懒加载库的图片缓存初始队列等到异步锁释放后再进行检查,否则存入缓存队列,等待下一帧再检查。
总结
优化过后,对应常见的机型基本能保证页面流畅不卡顿。chrome的performance图基本上和真机操作的情况保持一致,如果performance出现掉帧,那iPhone6s上和android上基本也会出现掉帧,但是iPhone7以上的机器却可能感受不明显。通过performance能够快速定位掉帧的问题,通过解决这些问题实质性的优化页面性能,而不是通过猜测进行无效优化。