说说微信小程序性能优化

344 阅读10分钟

问题

小程序启动会常常遇到小程序首次启动前,微信会在小程序启动前为小程序准备好通用的运行环境,如运行中的线程和一些基础库的初始化,然后才开始进入启动状态,展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作:

  • 下载小程序代码包
  • 加载小程序代码包
  • 初始化小程序首页

下载到的小程序代码包不是小程序的源代码,而是编译、压缩、打包之后的代码包

方案

围绕上图小程序的启动流程, 我们可以从加载、渲染两个纬度进行切入:

加载

提升体验最直接的方法是控制小程序包的大小,常见手段有如下:

  • 代码包的体积压缩可以通过勾选开发者工具中“上传代码时,压缩代码”选项
  • 及时清理无用的代码和资源文件
  • 减少资源包中的图片等资源的数量和大小(理论上除了小icon,其他图片资源从网络下载),图片资源压缩率有限

从开发者的角度看,控制代码包大小有助于减少小程序的启动时间。对低于1MB的代码包,其下载时间可以控制在929ms(iOS)、1500ms(Android)内

控制包大小

  • 代码包的体积压缩可以通过勾选开发者工具中“上传代码时,压缩代码”选项
  • 及时清理无用的代码(日志,方法,变量,类以及注释代码)和资源文件
  • 减少资源包中的图片等资源的数量和大小(理论上除了小icon,其他图片资源从网络下载),图片资源压缩率有限

分包加载

根据业务场景,将用户访问率高的页面放在主包里,将访问率低的页面放入子包里,按需加载;使用分包时需要注意代码和资源文件目录的划分。启动时需要访问的页面及其依赖的资源文件应放在主包中。同时也要注意子包不能太大。

分包预加载

在采用分包加载的基础上,当用户点击到子包的目录时,还是有一个代码包下载的过程,这会感觉到明显的卡顿,所以子包也不建议拆的太大,当然我们可以采用子包预加载技术,并不需要等到用户点击到子包页面后在下载子包。而是可以根据后期数据,做子包预加载,将用户在当先页可能点击的子包页面先加载,当用户点击后直接跳转;

这种基于配置的子包预加载技术,是可以根据用户网络类型来判断的,当用户处于网络条件好时才预加载;是灵活可控的

独立分包

目前很多小程序主包+子包(2M+6M)的方式,但是在做很多运营活动时,我们会发现活动(红包)是在子包里,但是运营、产品投放的落地页链接是子包链接,这是的用户在直达落地时,必须先下载主包内容(一般比较大),在下载子包内容(相对主包,较小),这使得在用户停留时间比较短的小程序场景中,用户体验不是很好,而且浪费了很大部分流量;

渲染

在微信小程序中,提高页面的多次渲染效率主要在于正确使用setData

避免使用不当setData

在数据传输时,逻辑层会执行一次JSON.stringify来去除掉setData数据中不可传输的部分,之后将数据发送给视图层。同时,逻辑层还会将setData所设置的数据字段与data合并,使开发者可以用this.data读取到变更后的数据。开发者在执行setData调用时,最好遵循以下原则:

  • 不要过于频繁调用setData,应考虑将多次setData合并成一次setData调用

    • 每次setData的调用都是一次进程间通信过程,通信开销与setData的数据量正相关:
    • setData会引发视图层页面内容的更新,这一耗时操作一定时间内会阻塞用户交互;
    • setData是小程序开发使用最频繁,也是最容易引发性能问题的。
  • 避免每次 setData 都传递大量新数据,数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示且数据结构比较复杂或包含长字符串,则不应使用setData来设置这些数据

    导致js编译过程变慢,例如在改变data中某个数组的某个数据项是,在setData是可采用只更改某个数据项。列表数据量过大时,也可采用分步渲染加载的方法。

    使用setData传输大量数据:通讯耗时与数据量正相关,页面更新延迟,可能造成页面更新开销增加。 使用时应仅传输页面中发生变化的数据,使用setData的特殊key实现局部更新

  • 与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下

    短时间内频繁调用setData,操作卡顿、交互延迟,阻塞通信,页面渲染延迟 避免不必要的setData,对连续的setData调用进行合并。

    也就是说避免将未绑定在 WXML 的变量传入 setData: setData操作会引起框架处理一些渲染界面相关的工作, 一个未绑定的变量意味着与界面渲染无关,传入setData会造成不必要的性能消耗。如数据需要在当前页面共享,可采用定义全局变量的方式(如使用this.)。

  • 利用setData进行列表局部刷新

    其实这个小操作对刚刚接触到微信小程序的人来说应该是不容易发现的,不理解setData还有这样的写法

    在一个列表中,有n条数据,采用上拉加载更多的方式,假如这个时候想对其中某一个数据进行点赞操作,还能及时看到点赞的效果

    1. 可以采用setData全局刷新,点赞完成之后,重新获取数据,再次进行全局重新渲染,这样做的优点是:方便,快捷!缺点是:用户体验极其不好,当用户刷量100多条数据后,重新渲染量大会出现空白期(没有渲染过来)

    2. 说到重点了,就是利用setData局部刷新

    <-- 将点赞的`id`传过去,知道点的是那一条数据, 将点赞的`id`传过去,知道点的是那一条数据 -->
    <view wx:if="{{!item.status}}" class="btn" data-id="{{index}}" bindtap="couponTap">立即领取</view>
    <-- 重新请求获取数据,查找相对应id的那条数据的下标(`index`是不会改变的) -->
    <-- 最后用setData进行局部刷新 -->
    this.setData({
        list[index] = newList[index]
    })
    
  • 切勿在后台页面进行setData

    在一些页面会进行一些操作,而到页面跳转后,代码逻辑还在执行,此时多个webview是共享一个js进程;后台的setData操作会抢占前台页面的渲染资源;

    后台页面进行setData,抢占前台页面的渲染资源 页面切入后台后的setData调用,延迟到页面重新展示时执行。

用户事件使用不当

视图层将事件反馈给逻辑层时,同样需要一个通信过程,通信的方向是从视图层到逻辑层。因为这个通信过程是异步的,会产生一定的延迟,延迟时间同样与传输的数据量正相关,数据量小于64KB时在30ms内。降低延迟时间的方法主要有两个。

  • 去掉不必要的事件绑定(WXML中的bindcatch),从而减少通信的数据量和次数;
  • 事件绑定时需要传输targetcurrentTargetdataset,因而不要在节点的data前缀属性中放置过大的数据。

页面结构复杂

初始渲染发生在页面刚刚创建时。初始渲染时,将初始数据套用在对应的WXML片段上生成节点树。节点树也就是在开发者工具WXML面板中看到的页面树结构,它包含页面内所有组件节点的名称、属性值和事件回调函数等信息。最后根据节点树包含的各个节点,在界面上依次创建出各个组件。在这整个流程中,时间开销大体上与节点树中节点的总量成正比例关系。因而减少WXML中节点的数量可以有效降低初始渲染和重渲染的时间开销,提升渲染性能。

  • 避免页面节点嵌套过深,增加页面渲染压力
  • 避免使用:active伪类来实现点击态,建议使用小程序内置组件的 'hover-class' 属性来实现。

使用自定义组件

自定义组件的更新只在组件内部进行,不受页面其他不能分内容的影响;比如一些运营活动的定时模块可以单独抽出来,做成一个定时组件,定时组件的更新并不会影响页面上其他元素的更新;

对于一些独立的模块我们尽可能抽离出来,这是因为自定义组件的更新并不会影响页面上其他元素的更新,各个组件也将具有各自独立的逻辑空间。每个组件都分别拥有自己的独立的数据、setData调用。

避免不当的使用onPageScroll

每一次事件监听都是一次视图到逻辑的通信过程,所以只在必要的时候监听pageSrcoll

代码层面

  • 避免使用.获取对象的深层属性,容易造成微信告警,可使用对象解构默认赋值,增加代码严谨性,不然小程序助手会告警报错。
  • 页面退出之前销毁定时器,可在onUnLoad生命周期钩子函数中执行
  • 运用wx-for时,为循环节点添加key属性,一般情况下不用index值作为key值
  • 减少页面的http请求次数,对于返回数据相同的接口,建议考虑使用数据缓存方式在当前页面存储数据。
  • 短时间内发起太多的图片请求,考虑使用图片懒加载和雪碧图方式

关于微信小程序首屏渲染优化

提前请求

异步请求可以在页面onLoad就加载,不需要等页面ready后在异步请求数据;当然,如果能在前置页面点击跳转时预请求当前页的核心异步请求,效果会更好;

请求可以在页面onLoad就加载,不需要等页面ready后在异步请求数据

利用缓存

利用storage API, 对变动频率比较低的异步数据进行缓存,二次启动时,先利用缓存数据进行初始化渲染,然后后台进行异步数据的更新,这不仅优化了性能,在无网环境下,用户也能很顺畅的使用到关键服务;

尽量减少不必要的https请求,可使用 getStorageSync() 及 setStorageSync() 方法将数据存储在本地

避免白屏

可以在前置页面将一些有用的字段带到当前页,进行首次渲染(列表页的某些数据--> 详情页),没有数据的模块可以进行骨架屏的占位,使用户不会等待的很焦虑,甚至走了;

可以在前置页面将一些有用的字段带到当前页,进行首次渲染(列表页的某些数据--> 详情页),没有数据的模块可以进行骨架屏的占位

及时反馈

及时的对需要用户等待的交互操作进行反馈,避免用户以为小程序卡了,无响应
最后一句
学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议。