小程序性能优化要点

指标:

  • 页面是否能正常访问?首次内容绘制(First Contentful Paint, FCP)
  • 页面内容是否有用?首次有效绘制(First Meaningful Paint,FMP)
  • 页面功能是否可用?可交互时间(Time to Interactive,TTI)

页面内容是否有用比较主观。难以规范化数值衡量。

对于小程序

  • 首次内容绘制 FCP = 白屏加载结束 = 页面开始展示的时间点 - 开始请求时间点(performance.Timing.navigationStart)。在 H5 中就是开始 head 的末尾插入 script,它的执行作为页面开始展示时间节点。在小程序里可以用 onLoad 生命周期回调触发时间替代吧?
  • 首次有效绘制 FMP = 首屏渲染完成 = 首屏幕最慢的图片加载完成时间(或者无图片就 performance.timing.domContentLoadedEventStart)- performance.Timing.navigationStart。
  • 可交互时间 TTI = performance.timing.domContentLoadedEventStart(页面元素加载完毕,图片未必加载完,但可以滚动,搜索操作)时间- performance.Timing.navigationStart。

小程序官方性能指标:

  • 首屏时间不超过 5 秒
  • 渲染时间不超过 500 ms
  • 每秒调用 setData 的次数不超过 20 次
  • setData 的数据再 JSON.stringify 后不超过 256 kb
  • 页面 WXML 的节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个。
  • 所有网络请求都在 1s 内返回结果;

小程序的最终渲染载体仍旧是浏览器内核,而非原生客户端。但给予性能考虑,它启用了双线程模型。

webview 线程负责视图渲染,逻辑层负责执行 JS 代码。逻辑层通过通过 setData 来传输给视图层数据渲染。然而任何线程间数据传递都有延时,所以通信是异步的,视图层没法控制具体的 DOM。

小程序加载流程

  • 准备运行环境。启动双线程环境,预先加载小程序基础库(包括 Webview 的基础库和 AppService 的基础库,前者注入到试图中,后者注入到逻辑层)。
  • 下载小程序代码包。初次启动需要下载编译后的代码包到本地,之后就会缓存代码。小程序代码表最终是经过 GZIP 压缩放在 CDN 上的。
  • 加载小程序代码包。下载完后执行代码包,此阶段内主包内所有页面 JS 都会被执行。并做页面注册,会调用 JS 里的 Page 构造器来初始化 JS 的数据、方法。
  • 初始化小程序首页。代码包加载完毕,基础库寻找到首页,初始化页面实例,并传递信息给视图层。视图层结合 WXML、WXSS,初始数据来渲染页面。

若是首次内容绘制 FCP 时间长,怎么优化?

这通常都是加载的代码包大引起的,则减小代码体积

  • 减少小程序代码包体积,剔除掉冗余代码,可以使用打包工具做 tree-shaking(需要创建 webpack 化的小程序项目)。还有像是 moment 有替代方案,去掉这个也能让小程序变小。再开启 GZIP 压缩。
  • 分包预下载。小程序提供的能力,可以在进入某个页面预下载接下来可能会用到的分包,避免切页面白屏。
  • 部分页面 H5 化。小程序提供了 web-view 组件,可以直接引用外部 H5 页,一方面这也能减少小程序包的体积,一方面让经常需要高度变化的内容扩展更改的更便捷。

若是首次有效绘制 FMP 时间长,怎么优化?

骨架屏

  • 上骨架屏吧,这是缩短 FMP 首屏渲染时间的重要手段,可以减缓用户的焦虑感。

图片优化

  • 减少静态资源文件的大小,使用 webp,合适的图片压缩工具,合理的剪裁降质来减少图片体积
  • CDN 图床加快图片的获取速度。
  • 降级加载大图资源。对于真的需要一张渲染很大的一张图片,可以预先使用高度压缩模糊的图片占位替代(给一个量化标准是 200kb以内),等到原图加载完毕再转移到真实节点上渲染。只需要一个 display: none 的 image 标签,就可以做到只加载图片资源,但不渲染。

接口拉取优化

  • 接口本地缓存;开启数据预拉取,小程序启动时,微信服务器可以代理小程序客户端获取 HTTP 请求,当小程序加载完后调用 wx.getBackgroundFetchData 从本地缓存拿数据。
  • 跳转时预拉取数据。从上个页面跳转到下一个页面,环境初始化以及页面实例化的工作需要耗时 300~400 ms,所以可以不在 onLoad 钩子触发再发起网络请求,取而代之可以在上一个页面 wx.navigateTo 调用前获取接口,并存储在全局 Promise 对象中,等待下一个页面加载完成再从 Promise 对象中读取数据。这也是双线程模型的优势,不同页面跳转不需要销毁全局对象。
  • 非关键渲染数据延迟请求。
  • 请求合并。wx.request(HTTP连接)的最大并发数量是 10 个,请求频繁的时候可以检查是否可以合并一段时间内的请求数据,做一个请求发送给服务器。

若是可交互时间 TTI 长,怎么优化

wx.navigateTo 跳转时

  • 准备新的 webview 线程环境,初始化基础库
  • 从逻辑层到视图层初始数据通信
  • 视图层结合 WXML、WXSS,初始数据来渲染页面。

切换时主要性能损耗在 数据通信节点数创建/更新

  • 降低线程通信频次
  • 减少通信数据量
  • 减少 WXML 节点数量。
  • 减少事件数量及触发次数

做法有

  • 合并 setData 调用
  • 只把与渲染渲染相关的数据放到 data 中。
  • 应用层数据 diff。只要调用 setData 那就必然会有视图层渲染(当然视图层自己也会合并 data 数据另找时间渲染),那么开发者自己可以比对两次数据是否有变化,有变化再 setState
  • 去掉不必要的事件绑定。事件信息也需要从视图层通信反馈给逻辑层,所以尽量减少不必要的事件绑定,尤其是 onPageScroll 里面还 setData 的这种。
  • 适当的组件颗粒度。嵌套层级不能太深,组件数量和小程序代码包大小正相关。但也不能往一个组件上挂载太多的东西。

内存占用高?怎么优化

内存占用高,内存空间会被系统销毁,或者被微信客户端主动回收,导致小程序 Crash

小程序提供了监听内存不足告警的事件 API:wx.onMemoryWarning,可以告知开发者内存紧张。但开发者无法操作内存资源,顶多调用 wx.reLaunch 来清理页面栈,重新加载页面,来降低内存负荷。但开发者更应该仔细取收集告警信息上报到日志系统,分析并对程序做优化。

  • 回收后台页面计时器。从上一个页面切换到下一个页面,上一个页面的定时器 setTimeout 和 setTimeInterval 仍旧会继续执行,可能就会占用过多的内存资源,onHide 时清理掉,onShow 时再恢复。比如说小程序的 <swiper> 组件,进入后台时仍旧继续轮播。
  • 避免频发事件中重度内存操作。比如说导航栏吸顶效果,onPageScroll 事件回调必须使用节流函数,并在它的回调中避免使用 setData。部分场景尽量使用 IntersectionObserver API。
  • 大图,长列表优化。快速滚动长列表,被销毁的组件可能来不及加载完。小程序提供了长列表组件,virtual List

参考链接

京东京喜小程序的高性能打造之路

掘金-小程序页面加载性能优化

如何在 H5 和小程序项目中计算白屏时间和首屏时间,说说你的思路

小程序性能优化要点

上一篇:排序算法总结-python实现


下一篇:微信小程序之API定时器setTimeout setInterval clearTimeout clearInterval