前端面试超全整理8(小程序性能)

32、小程序性能背景

京喜小程序自去年双十一上线微信购物一级入口后,时刻迎接着亿级用户量的挑战,细微的体验细节都有可能被无限放大,为此,“极致的页面性能”、“友好的产品体验” 和 “稳定的系统服务” 成为了我们开发团队的最基本执行原则。

首页作为小程序的门户,其性能表现和用户留存率息息相关。因此,我们对京喜首页进行了一次全方位的升级改造,从加载、渲染和感知体验几大维度深挖小程序的性能可塑性。

除此之外,京喜首页在微信小程序、H5、APP 三端都有落地场景,为了提高研发效率,我们使用了 Taro[1] 框架实现多端统一,因此下文中有部分内容是和 Taro 框架息息相关的。

怎么定义高性能?

提起互联网应用性能这个词,很多人在脑海中的词法解析就是,“是否足够快?”,似乎加载速度成为衡量系统性能的唯一指标。但这其实是不够准确的,试想一下,如果一个小程序加载速度非常快,用户花费很短时间就能看到页面的主体内容,但此时搜索框却无法输入内容,功能无法被流畅使用,用户可能就不会关心页面渲染有多快了。所以,我们不应该单纯考虑速度指标而忽略用户的感知体验,而应该全方位衡量用户在使用过程中能感知到的与应用加载相关的每个节点。

谷歌为 Web 应用定义了以用户为中心的性能指标体系,每个指标都与用户体验节点息息相关:

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

其中,“是否有用?” 这个问题是非常主观的,对于不同场景的系统可能会有完全不一样的回答,所以 FMP 是一个比较模糊的概念指标,不存在规范化的数值衡量。

小程序作为一个新的内容载体,衡量指标跟 Web 应用是非常类似的。对于大多数小程序而言,上述指标对应的含义为:

  • FCP:白屏加载结束;
  • FMP:首屏渲染完成;
  • TTI:所有内容加载完成;

综上,我们已基本确定了高性能的概念指标,接下来就是如何利用数值指标来描绘性能表现。

小程序官方性能指标

小程序官方针对小程序性能表现制订了权威的数值指标,主要围绕 渲染表现setData 数据量元素节点数网络请求延时 这几个维度来给予定义(下面只列出部分关键指标):

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

详见 小程序性能评分规则[2]

我们应该把这一系列的官方指标作为小程序的性能及格线,不断地打磨和提升小程序的整体体验,降低用户流失率。另外,这些指标会直接作为小程序体验评分工具的性能评分规则(体验评分工具会根据这些规则的权重和求和公式计算出体验得分)。

我们团队内部在官方性能指标的基础上,进一步浓缩优化指标系数,旨在对产品体验更高要求:

  • 首屏时间不超过 2.5 秒;
  • setData 的数据量不超过 100kb;
  • 所有网络请求都在 1 秒内返回结果;
  • 组件滑动、长列表滚动无卡顿感;

体验评分工具

小程序提供了 体验评分工具(Audits 面板)[3] 来测量上述的指标数据,其集成在开发者工具中,在小程序运行时实时检查相关问题点,并为开发者给出优化建议。

img体验评分面板

以上截图均来自小程序官方文档

体验评分工具是目前检测小程序性能问题最直接有效的途径,我们团队已经把体验评分作为页面/组件是否能达到精品门槛的重要考量手段之一。

小程序后台性能分析

我们知道,体验评分工具是在本地运行小程序代码时进行分析,但性能数据往往需要在真实环境和大数据量下才更有说服力。恰巧,小程序管理平台小程序助手 为开发者提供了大量的真实数据统计。其中,性能分析面板从 启动性能运行性能网络性能 这三个维度分析数据,开发者可以根据客户端系统、机型、网络环境和访问来源等条件做精细化分析,非常具有考量价值。

img小程序助手性能分析

其中,启动总耗时 = 小程序环境初始化 + 代码包加载 + 代码执行 + 渲染耗时

第三方测速系统

很多时候,宏观的耗时统计对于性能瓶颈点分析往往是杯水车薪,作用甚少,我们需要更细致地针对某个页面某些关键节点作测速统计,排查出暴露性能问题的代码区块,才能更有效地针对性优化。京喜小程序使用的是内部自研的测速系统,支持对地区、运营商、网络、客户端系统等多条件筛选,同时也支持数据可视化、同比分析数据等能力。京喜首页主要围绕 页面 onLoadonReady数据加载完成首屏渲染完成各业务组件首次渲染完成 等几个关键节点统计测速上报,旨在全链路监控性能表现。

img内部测速系统

另外,微信为开发者提供了 测速系统[4],也支持针对客户端系统、网络类型、用户地区等维度统计数据,有兴趣的可以尝试。

了解小程序底层架构

为了更好地为小程序制订性能优化措施,我们有必要先了解小程序的底层架构,以及与 web 浏览器的差异性。

微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。

而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了双线程模型

  • 视图层:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;
  • 逻辑层:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;

img双线程模型图

上图来自小程序官方开发指南

然而,任何线程间的数据传输都是有延时的,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线程之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。

作为小程序开发者,我们常常会被下面几个问题所困扰:

  • 小程序启动慢;
  • 白屏时间长;
  • 页面渲染慢;
  • 运行内存不足;

接下来,我们会结合小程序的底层架构分析出这些问题的根本原因,并针对性地给出解决方案。

小程序启动太慢?

小程序启动阶段,也就是如下图所示的展示加载界面的阶段。

img小程序加载界面

在这个阶段中(包括启动前后的时机),微信会默默完成下面几项工作:

1. 准备运行环境:

在小程序启动前,微信会先启动双线程环境,并在线程中完成小程序基础库的初始化和预执行。

小程序基础库包括 WebView 基础库和 AppService 基础库,前者注入到视图层中,后者注入到逻辑层中,分别为所在层级提供其运行所需的基础框架能力。

2. 下载小程序代码包:

在小程序初次启动时,需要下载编译后的代码包到本地。如果启动了小程序分包,则只有主包的内容会被下载。另外,代码包会保留在缓存中,后续启动会优先读取缓存。

3. 加载小程序代码包:

小程序代码包下载好之后,会被加载到适当的线程中执行,基础库会完成所有页面的注册。

在此阶段,主包内的所有页面 JS 文件及其依赖文件都会被自动执行。

在页面注册过程中,基础库会调用页面 JS 文件的 Page 构造器方法,来记录页面的基础信息(包括初始数据、方法等)。

4. 初始化小程序首页:

在小程序代码包加载完之后,基础库会根据启动路径找到首页,根据首页的基础信息初始化一个页面实例,并把信息传递给视图层,视图层会结合 WXML 结构、WXSS 样式和初始数据来渲染界面。

综合考虑,为了节省小程序的“点点点”时间(小程序的启动动画是三个圆点循环跑马灯),除了给每位用户发一台高配 5G 手机并顺带提供千兆宽带网络之外,还可以尽量 控制代码包大小,缩小代码包的下载时间。

无用文件、函数、样式剔除

经过多次业务迭代,无可避免的会存在一些弃用的组件/页面,以及不被调用的函数、样式规则,这些冗余代码会白白占据宝贵的代码包空间。而且,目前小程序的打包会将工程下所有文件都打入代码包内,并没有做依赖分析。

因此,我们需要及时地剔除不再使用的模块,以保证代码包空间利用率保持在较高水平。通过一些工具化手段可以有效地辅助完成这一工作。

  • 文件依赖分析

在小程序中,所有页面的路径都需要在小程序代码根目录 app.json 中被声明,类似地,自定义组件也需要在页面配置文件 page.json 中被声明。另外,WXML、WXSS 和 JS 的模块化都需要特定的关键字来声明依赖引用关系。

WXML 中的 importinclude