微信小程序性能优化方案
微信小程序如果想要优化性能,有关键性的两点:
- 提高加载性能
- 提高渲染性能
提高加载性能
首先,我们需要了解,当用户点击小程序后小程序启动的流程,具体详细的流程在微信官方文档
启动性能
中有描述,以下的篇章我们主要着重于我们能够改变优化的环节。
小程序代码包准备(下载代码包) => 开发者代码注入(逻辑和渲染) => 首页(初次)渲染
小程序代码包准备(下载代码包)
下载代码包提升性能的最关键性一点就是,控制代码包的大小。
至于如何控制包的大小?
-
分包加载
我在另一篇博客中具体讲解了分包加载,大家可以移步去学习
小程序分包加载实践
-
代码重构和优化
通过代码重构,降低代码冗余。在使用如 Webpack 等打包工具时,要尽量利用 tree-shaking 等特性去除冗余代码,也要注意防止打包时引入不需要的库和依赖。 -
控制代码包内图片等资源
避免在代码包中包含过多、过大的图片,应尽量采用网络图片。 -
及时清理没有使用到的代码和资源
目前小程序打包是会将工程下所有文件都打入代码包内,因此需要及时清理已经没有被实际使用到的库文件和资源也。
开发者代码注入
-
减少启动过程的同步调用
在小程序启动流程中,会注入开发者代码并顺序同步执行 App.onLaunch, App.onShow, Page.onLoad, Page.onShow。
在小程序初始化代码(Page,App 定义之外的内容)和启动相关的几个生命周期中,应避免执行复杂的计算逻辑或过度使用Sync结尾的同步API,如 wx.getStorageSync,wx.getSystemInfoSync 等。
对于 getSystemInfo, getSystemInfoSync 的结果应进行缓存,避免重复调用。 -
使用懒注入
通常情况下,在小程序启动时,启动页面所在分包和主包(独立分包除外)的所有JS代码会全部合并注入,包括其他未访问的页面以及未用到自定义组件,造成很多没有使用的代码注入到小程序运行环境中,影响注入耗时和内存占用。
自基础库版本 2.11.1 起,小程序支持仅注入当前页面需要的自定义组件和当前页面代码,以降低小程序的启动时间和运行时内存。开发者可以在 app.json 中配置:
{
"lazyCodeLoading": "requiredComponents"
}
页面渲染优化
-
提前首屏数据请求
数据预拉取
:能够在小程序冷启动的时候通过微信后台提前向第三方服务器拉取业务数据,当代码包加载完时可以更快地渲染页面,减少用户等待时间,从而提升小程序的打开速度。
周期性更新
:在用户未打开小程序的情况下,也能从服务器提前拉取数据,当用户打开小程序时可以更快地渲染页面,减少用户等待时间。
-
骨架屏
在页面数据未准备好时(如需要通过网络获取),尽量避免展示空白页面,应先通过骨架屏展示页面的大致结构,请求数据返回后在进行页面更新,以提升用户的等待意愿。 -
缓存请求数据
小程序提供了wx.setStorage、wx.getStorage等读写本地缓存的能力,数据存储在本地,返回的会比网络请求快。可以优先从缓存中获取数据来渲染视图,等待网络请求返回后进行更新。 -
精简首屏数据
延迟请求非关键渲染数据,与视图层渲染无关的数据尽量不要放在 data 中,加快页面渲染完成时间。
提升渲染性能
setData工作原理
小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。
而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。
每调用一次setData, 都是逻辑层向渲染层的一次通讯,这个通信还不是直接传给webView, 而是通过走了native层,通讯的开销很大。
渲染层收到通讯后,还需要重新渲染出来,所以,一次setData带来两次开销:通信的开销 + webview更新的开销。
优化方法
-
减少setData的数据量
如果一个数据不能会影响渲染层,则不用放在setData里面 -
合并setData的请求,减少通讯的次数
-
列表的局部更新
在一个列表中,有n条数据,当前需求为实现点赞效果。
首先,我们可以先展示效果,再进行异步请求,只在请求失败的情况下提示。
其次,我们可以采用布局刷新,根据点赞数据的id和index分别获取数据以及更新数据
this.setData({
list[index] = newList[index]
})
-
小心后台页面的js
小程序中可能有n个页面,所有的这些页面,虽然都拥有自己的webview(渲染层), 但是却共享同一个js运行环境。也就是说,当你跳到了另外一个页面(假设是B页面),本页面(假设是A页面)的定时器等js操作仍在进行,并且不会被销毁,并且会抢占B页面的资源。
在h5的环境中,当我们跳转到其他页面,老页面的js环境会被自动销毁,定时器什么都被销毁掉了,因此我们不需要关心老页面中,还有哪些js代码可能还会执行。但是在小程序中,我们必须手动的“清理”掉这样的代码。
-
小心onPageScroll
pageScroll 事件,也是一次通讯,是webview层向js逻辑层的通讯。这个事件很容易被频繁调用,开销较大,并且假如回调函数有复杂的setData的话, 性能就会很差了。
-
合理使用小程序组件
自定义组件的更新只在组件内部进行,不会影响页面其他元素。因为各个组件具有独立的逻辑空间、数据、样式环境及 setData 调用。
基于自定义组件的 Shadow DOM 模型设计,我们可以将页面中一些需要高频执行 setData 更新的功能模块(如倒计时、进度条等)封装成自定义组件嵌入到页面中。
当这些自定义组件视图需要更新时,执行的是组件自己的 setData,新旧节点树的对比计算和渲染树的更新都只限于组件内有限的节点数量,有效降低渲染时间开销。 -
图片懒加载
渲染页面时,只渲染出现在视图范围内的元素。
IntersectionObserver
微信提供了IntersectionObserver对象,用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见
常规的做法是,通过getBoundingClientRect()获取元素的位置,然后与页面滚动位置比较,如果出现在视图内,就将img显示。这种方式有2个问题
getBoundingClientRect()方法调用本身容易引起页面重排
监听滚动事件本身就频繁触发,虽然可以通过节流的方式来减少,但还是容易增加无谓代码处理
<img class="img-{{index}}" wx:for="{{data}}" wx:if="{{item.imgShow}}" wx:key="index"></img>
let data = list;
data.forEach((item,index)=>{
this.createIntersectionObserver().relativeToViewport.observe(`.img-${index}`,res=>{
if (res.intersectionRatio > 0){
this.setData({
item.imgShow:true
})
}
})
})
intersectionRatio值大于0,说明元素出现在视图中了,显示图片组件。
-
善用onReady和onLoad
onLoad是页面加载阶段,在onLoad阶段异步请求页面展示数据,onReady是页面加载完成阶段,对于页面之后需要展示或使用,但刚开始并不展示的数据可以在onReady阶段再进行请求加载。