一、前言
小程序的性能又可以分为「启动性能」和「运行时性能」。「启动性能」让用户能够更快地打开并看到小程序的内容,「运行时性能」保障用户能够流畅地使用小程序的功能。除了本身的功能之外,良好性能带来的良好用户体验,也是小程序能够留住用户的关键。
二、优化性能诊断工具
诊断工具能让开发者更好地知道性能瓶颈在哪里,并且能让用户在优化后,更好地知道效果如何。微信官方提供的性能诊断工具有:
1. 代码依赖分析
该工具可以帮助开发者分析代码中的依赖关系,以便更好地优化代码的结构和性能。通过分析代码的依赖关系,开发者可以确定哪些代码模块是最重要的,以及哪些模块可能需要进行重构或优化。该工具可以帮助开发者分析代码中的依赖关系,以便更好地优化代码的结构和性能。通过分析代码的依赖关系,开发者可以确定哪些代码模块是最重要的,以及哪些模块可能需要进行重构或优化。
2. 性能报告
该工具可以为开发者提供有关应用程序性能的详细信息,包括加载时间、响应时间、资源使用情况等。这些信息可以帮助开发者确定应用程序的性能瓶颈,并采取相应的措施来提高应用程序的性能。
3. 代码质量扫描
该工具可以帮助开发者分析代码的质量,以便更好地管理和维护代码。通过分析代码的质量,开发者可以确定哪些代码模块需要进行重构或优化,以便更好地满足业务需求并提高代码的可维护性和可扩展性。
4.调试区的 Performance 面板
该工具可以帮助开发者分析应用程序的性能,包括 CPU、内存、网络和渲染等方面。通过 Performance 面板,开发者可以查看代码执行的时间线、函数调用栈等信息,以便更好地确定性能瓶颈并进行优化。
5.Memory 面板
该工具可以帮助开发者分析应用程序的内存使用情况。通过 Memory 面板,开发者可以查看内存使用情况的时间线、对象分配情况、垃圾收集情况等信息,以便更好地确定内存泄漏等问题并进行优化。
6.JavaScript Profiler 面板
该工具可以帮助开发者分析 JavaScript 代码的执行情况。通过 JavaScript Profiler 面板,开发者可以查看 JavaScript 代码的执行时间、函数调用次数、内存使用情况等信息,以便更好地确定 JavaScript 代码的性能瓶颈并进行优化。
7.体验评分(Audits)面板
该工具可以帮助开发者评估应用程序的用户体验。通过 Audits 面板,开发者可以查看应用程序在加载速度、响应速度、可访问性、SEO优化等方面的得分情况,以便更好地确定哪些方面需要进行优化以提高用户体验。
8.对于具体的业务代码,我们通过打时间戳的形式,数字化业务逻辑执行的时间,显性量化优化后的效果
开发者量化业务逻辑的执行时间,以便更好地确定业务逻辑的性能瓶颈并进行优化。通过在关键代码位置打时间戳并记录代码执行时间,开发者可以比较不同版本之间的执行时间差异,以便更好地确定优化效果。
三、启动性能优化
小程序的加载基本流程:
3.1 商品主图业务优化
行业内商详页面模板元素大致相同,在进入页面时,映入眼帘的肯定是商品主图。一般商户的主图照片都会十分高清,资源较大。对于性能来说是个较大消耗,如果简单压缩,就没办法满足高清的业务需求。基于以上背景,商详页对于主图照片有个特定的业务优化。
该方法说得简单点就是先加载清晰度差一点的商详照片,等非高清照片加载完成。在回调事件中。无缝替换成高清大图,用户无感知。
具体实现方案:
a)技术实现方案
首先在图片中预加载 2 个元素。分别加载资源非高清大图:
{picFilter(item || '//:0','md', pictureRatio)
以及高清大图
picFilter(item || '//:0','lg', pictureRatio)
利用 hasLoad 变量控制元素加载。加载不同清晰度的图片方法是 picFilter,lg 是高清大图,md 表示非高清大图。
<swiper-item> <image wx:if="{{index === 0 && !hasLoad}}" bind:load="loadImg" binderror="loadImg" catchtap="showBannerPic" data-picarr="{{item}}" data-picsrc="{{item}}" src="{{picFilter(item || '//:0', goodsInfo.imagePx || 'md', 10)}}" class="slide-image slide-first-picture" mode="aspectFill" ></image> <image wx:elif="{{ getShow(index, loopIndex, goodsInfo.goodsVideoUrl) }}" catchtap="showBannerPic" data-picarr="{{goodsInfo.goodsImageUrl}}" data-picsrc="{{item}}" src="{{picFilter(item || '//:0','lg', pictureRatio)}}" class="slide-image" mode="aspectFill" ></image></swiper-item><image wx:if="{{ goodsInfo.goodsImageUrl.length > 0 && isLoadBg }}" bind:load="loadEvent" binderror="loadEvent" src="{{picFilter(goodsInfo.goodsImageUrl[0] || '//:0','lg', pictureRatio)}}" mode="aspectFill" class="hide-image slide-image"></image>
在非高清图片加载完成后, 预加载高清图片。
loadImg(evt) { // 加载看不见的图片,预加载 this.setData({ isLoadBg: true, }); }, loadEvent() { // 预加载完成,setData this.setData({ hasLoad: true, }); },
b)优化效果
我们选取了一个高质量的图片进行了测试。发现图片在优化前从 120kb 到优化后的 9.6kb。资源加载大大减小了。
优化前:
优化后:
3.2按需注入
在 app 中加入全局配置。可以有效降低小程序的启动时间和运行内存。
{
"lazyCodeLoading": "requiredComponents"
}
3.3分包异步化
分包异步化是微信官方提供的分包加载的优化方案。目前来说多渠道小程序-支付宝也已支持,并且商品详情页也已经投入生产环境使用。分包异步化是将小程序的分包从页面粒度细化到组件甚至文件粒度。这使得本来只能放在主包内页面的部分插件、组件和代码逻辑可以剥离到分包中,并在运行时异步加载,从而进一步降低启动所需的包大小和代码量。
3.4跨分包的自定义组件引用优化
商详分包有引用大量的其他分包的自定义组件,例如商详模板的装修组件(@design-platform/wx-sdk/index),浏览未购组件(platform://ec/browseLogCollector),快速下单组件(ec_order/fastTrade),收银台组件。因为加载商详时,由于其他分包还未下载或注入,其他分包的组件处于不可用的状态。所以我们首先需要设置占位组件,渲染占位组件作为替代,分包下载完成再进行替换。核心原理与商详主图业务的优化大致相同。不过该方案是小程序官方自行提供的。
占位组件使用方式:
{
"usingComponents": {
"pay-payment": "package://payment_cashier/pay-payment",
"browse-log-collector": "platform://ec/browseLogCollector",
"design-sdk": "@design-platform/wx-sdk/index"
},
"componentPlaceholder": {
"pay-payment": "view",
"browse-log-collector": "view",
"design-sdk": "view"
}
}
3.5非首屏组件懒加载
商详装修详情组件内嵌了装修 SDK,业务上有很多定制组件需要显示,包括商品描述。但这些组件应该属于非首屏组件。商详利用滚动加载的方式,将这些组件的渲染以及加载延迟。
技术上利用 isShowDesc 变量控制组件加载以及渲染。
onPageScroll({ scrollTop }) {
this.setData({
isShowDesc: true
});
}
该方案存在一种场景:首屏组件特别少,商品描述组件在首屏就无法展示出来了。所以商品详情页加载首屏组件后需要判断商品描述组件的可视性。
const queryDom = wx.createSelectorQuery().in(this);
const queryGoodsDes = queryDom.select('#detail');
if (queryGoodsDes) {
queryGoodsDes
.boundingClientRect((rect) => {
if (!rect) return;
if ((wx.rprm as any)?.systemInfo.windowHeight - rect.top > rect.height) {
this.setData(
{
isShowDesc: true,
},
() => this.emitGoodsDetail(),
);
}
})
.exec();
}
3.6文描图片的懒加载
上面虽然对于文件进行了懒加载,但是商品详情页文描大部分的场景都是图片,并且文描又是商户设置的动态文本。所以文描组件基本都是一次性发大量图片请求的场景。
一次发送大量的 http 请求是非常耗时的。
针对此场景,商品详情页利用元素可视性也进行了该场景的业务优化。
this.setData({ parseHtml }, () => { // 此处做滚动到图片位置的时候加载图片 parseHtml.images.forEach((item, index) => { const { obList } = this.data; const idx = item.index.replace(/(\d+)\.?/g, (s, $1) => `nodes[${$1}].`); if (!this.data.imgLoayLoad) { this.setData({ [`parseHtml.${idx}imgShow`]: true, [`parseHtml.${idx}imgLoayLoad`]: this.data.imgLoayLoad, }); return; } if (obList[index]) { obList[index].disconnect(); } obList[index] = this.createIntersectionObserver().relativeToViewport({ bottom: 750 }); obList[index].observe(`.wx-parse-img${item.imgIndex}`, (res) => { const idx = item.index.replace(/(\d+)\.?/g, (s, $1) => `nodes[${$1}].`); if (res.intersectionRatio > 0) { obList[index].disconnect(); this.setData({ [`parseHtml.${idx}imgShow`]: true, [`parseHtml.${idx}imgLoayLoad`]: this.data.imgLoayLoad, }); } }); }); });
3.7依赖分析指标展示优化前后变化
商品业务繁重,我们在优化前对于商品依赖进行了分析,剔除重复引用,将很多商品相关业务,海报业务全部放进一个单独的分包。以及非商品提供的能力利用按需注入、分包异步化、分包异步化、跨分包的自定义组件引用优化等方案进行优化
商品依赖分析:
优化前后分包大小变化:
优化前:1179kb
优化后:624kb
四、运行性能优化
4.1商祥数据出参数优化
商祥的 spu 数据字段是比较多,为了拼团的两套购买逻辑,做了两套 spu 数据来维护,这样无行之中又使数据变大了,结合这种场景,我们把两套 spu 改为维护一套,拼团业务的单独购买,重新获取 spu 数据做业务逻辑。
4.2setData 优化
setData 是小程序中使用最多,也是最容易引发性能问题的接口。
由于微信官方文档可知。小程序的逻辑层和视图层是两个独立的运行环境、分属不同的线程或进程,不能直接进行数据共享,需要进行数据的序列化、跨线程/进程的数据传输、数据的反序列化,因此数据传输过程是异步的、非实时的。
数据传输的耗时与数据量的大小正相关,如果对端线程处于繁忙状态,数据会在消息队列中等待。
因此在使用上遵循小程序的规范。
const BulletChatConfig = {
// 消费弹幕配置
};
Component({
data: {
originalData: [],
},
bulletChatConfig:bulletChatConfig,
pageLifetimes: {
show() {
this.animateStart();
},
}
});
- 页面或组件与渲染无关的数据。
- 最好挂在 this 下面。
- 控制 setData 的频率。对于连续的 setData 进行合并。
- 选择合适的 setData 范围。
- setData 应只传发生变化的数据。
// before: ? 不要在setData中偷懒一次性传所有的data;
this.setData({
singleSkuData: {
...skuData,
selectedMap: this.data.selectedMap,
},
});
// after
this.setData({
['singleSkuData.selectedMap']: this.data.selectedMap,
});
4.3选择高性能的动画实现方式
动画循环是前端的一个消耗。商详页也存在动画循环的组件。例如消费弹幕。秒杀定时器等。
起初商品详情页消费弹幕的动画循环是利用定时器 setTimeOut 去实现的。
const that = this;
const timer = setInterval(function () {
if (dataIndex === data.length) {
dataIndex = 0;
}
if (queueIndex === rowQueue.length) {
queueIndex = 0;
}
tempData.push(that.getBulletChatItem(data[dataIndex], rowQueue[queueIndex], config.itemStyle));
if (tempData.length > 8) {
tempData.splice(0, 1);
}
that.setData({
bulletChatList: tempData,
});
dataIndex++;
queueIndex++;
}, config.intervalTime);
that.setData({ timer, type: customType || DisplayType[display] || 'scrollup', bulletChatConfig: config });
改成 createIntersectionObserver 的可视性来进行循环动画。
this.intersectionObserver = wx.createIntersectionObserver(this, { observeAll: true, thresholds: [0, 0.5, 1, 0] });
if (!this.intersectionObserver) return;
this.intersectionObserver.relativeTo('.bullet-chat').observe('.bullet-chat-item-container', (res) => {
if (res.intersectionRatio >= 0.5) {
let {
dataset: { id },
} = res;
if (id === data.length - 1) {
id = -1;
}
this.setData({
[`bulletChatList[${id + 1}].style`]: config.itemStyle,
});
}
if (res.intersectionRatio === 0 && res.intersectionRect.width === 0) {
const {
dataset: { id },
} = res;
this.setData({
[`bulletChatList[${id}].style`]: 'left: 100%',
});
}
})
五、运优化结果展示
我们利用打时间戳的形式,对于小程序的各种业务逻辑时间,以及渲染时间进行量化。显性得出优化前后的各种指标变化。
优化前:
优化后:
六、总结
以上就是商详页面的性能优化实践,分析小程序的启动过程。可知小程序优化可分成运行性能和启动性能优化。
启动性能优化利用商品主图优化,文描图片的懒加载,非首屏组件的懒加载,分包异步化,按需注入等方案实践。利用小程序的依赖分析可看出商祥分包的确肉眼可见地变小了,首屏组件也变快了。
运行时性能优化方案包括商品详情页数据出参的优化,setData 的一些优化,选择高性能的动画实现方式的优化等。
我们利用打时间戳的方式。将商品详情页分成初始化,请求,渲染等过程,分别计时。对比出优化前后的指标变化,由指标可知,商祥从加载到用户可交互的时间确实从 2.5s 优化到 0.9s 左右,优化了近 50%。
优化前后对比:
优化前
优化后
作者:郑成文
来源:微信公众号:微盟技术中心
出处:https://mp.weixin.qq.com/s/KrDwmb8rMHE5e5hANmDt2w