前言
淘宝新势力周 (春上新) 是命运石 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 能够快速定位掉帧的问题, 通过解决这些问题实质性的优化页面性能, 而不是通过猜测进行无效优化.
上面的优化抽离出两个库:
kimi 链路的模块懒加载库: market-app-kimi-lazy
图片懒加载的异步库: km-lazyload-async
来源: https://segmentfault.com/a/1190000014359615