四时宝库

程序员的知识宝库

小程序商品页性能优化开发实践(小程序首页优化)


一、前言

小程序的性能又可以分为「启动性能」和「运行时性能」。「启动性能」让用户能够更快地打开并看到小程序的内容,「运行时性能」保障用户能够流畅地使用小程序的功能。除了本身的功能之外,良好性能带来的良好用户体验,也是小程序能够留住用户的关键。


二、优化性能诊断工具

诊断工具能让开发者更好地知道性能瓶颈在哪里,并且能让用户在优化后,更好地知道效果如何。微信官方提供的性能诊断工具有:

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 个元素。分别加载资源非高清大图:

Bash
{picFilter(item || '//:0','md', pictureRatio)

以及高清大图

Bash
picFilter(item || '//:0','lg', pictureRatio)

利用 hasLoad 变量控制元素加载。加载不同清晰度的图片方法是 picFilter,lg 是高清大图,md 表示非高清大图。

Bash
<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>

在非高清图片加载完成后, 预加载高清图片。

Bash
loadImg(evt) {    // 加载看不见的图片,预加载    this.setData({      isLoadBg: true,    });  },  loadEvent() {    // 预加载完成,setData    this.setData({        hasLoad: true,    });  },


b)优化效果

我们选取了一个高质量的图片进行了测试。发现图片在优化前从 120kb 到优化后的 9.6kb。资源加载大大减小了。

优化前:


优化后:


3.2按需注入


在 app 中加入全局配置。可以有效降低小程序的启动时间和运行内存。

Bash
{
  "lazyCodeLoading": "requiredComponents"
}


3.3分包异步化


分包异步化是微信官方提供的分包加载的优化方案。目前来说多渠道小程序-支付宝也已支持,并且商品详情页也已经投入生产环境使用。分包异步化是将小程序的分包从页面粒度细化到组件甚至文件粒度。这使得本来只能放在主包内页面的部分插件、组件和代码逻辑可以剥离到分包中,并在运行时异步加载,从而进一步降低启动所需的包大小和代码量。


3.4跨分包的自定义组件引用优化

商详分包有引用大量的其他分包的自定义组件,例如商详模板的装修组件(@design-platform/wx-sdk/index),浏览未购组件(platform://ec/browseLogCollector),快速下单组件(ec_order/fastTrade),收银台组件。因为加载商详时,由于其他分包还未下载或注入,其他分包的组件处于不可用的状态。所以我们首先需要设置占位组件,渲染占位组件作为替代,分包下载完成再进行替换。核心原理与商详主图业务的优化大致相同。不过该方案是小程序官方自行提供的。

占位组件使用方式:

Bash
{
  "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 变量控制组件加载以及渲染。

Bash
onPageScroll({ scrollTop }) {
    this.setData({
      isShowDesc: true
    });
  }

该方案存在一种场景:首屏组件特别少,商品描述组件在首屏就无法展示出来了。所以商品详情页加载首屏组件后需要判断商品描述组件的可视性。

Bash
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 请求是非常耗时的。

针对此场景,商品详情页利用元素可视性也进行了该场景的业务优化。

Bash
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 是小程序中使用最多,也是最容易引发性能问题的接口。

由于微信官方文档可知。小程序的逻辑层和视图层是两个独立的运行环境、分属不同的线程或进程,不能直接进行数据共享,需要进行数据的序列化、跨线程/进程的数据传输、数据的反序列化,因此数据传输过程是异步的、非实时的。

数据传输的耗时与数据量的大小正相关,如果对端线程处于繁忙状态,数据会在消息队列中等待。

因此在使用上遵循小程序的规范。

Bash
const BulletChatConfig = {
    // 消费弹幕配置
  };
  Component({
    data: {
      originalData: [],
    },
    bulletChatConfig:bulletChatConfig,
    pageLifetimes: {
      show() {
        this.animateStart();
      },
    }
  });

  • 页面或组件与渲染无关的数据。
  • 最好挂在 this 下面。
  • 控制 setData 的频率。对于连续的 setData 进行合并。
  • 选择合适的 setData 范围。
  • setData 应只传发生变化的数据。
Bash
// before: ? 不要在setData中偷懒一次性传所有的data;
 this.setData({
    singleSkuData: {
      ...skuData,
      selectedMap: this.data.selectedMap,
    },
});

Bash
// after
  this.setData({
    ['singleSkuData.selectedMap']: this.data.selectedMap,
  });


4.3选择高性能的动画实现方式

动画循环是前端的一个消耗。商详页也存在动画循环的组件。例如消费弹幕。秒杀定时器等。

起初商品详情页消费弹幕的动画循环是利用定时器 setTimeOut 去实现的。

Bash
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 的可视性来进行循环动画。

Bash
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

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接