微信小程序渲染性能优化总结

小程序渲染原理

render

双线程下的界面渲染,小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变动的时候,咱们须要经过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再通过对比先后差别,把差别应用在原来的Dom树上,渲染出正确的UI界面。javascript

由此可得:页面初始化的时间大体由页面初始数据通讯时间初始渲染时间两部分构成。其中,数据通讯的时间指数据从逻辑层开始组织数据到视图层彻底接收完毕的时间,传输时间与数据量大致上呈现正相关关系,传输过大的数据将使这一时间显著增长。于是减小传输数据量是下降数据传输时间的有效方式java

影响性能的关键因素

  1. 频繁的去setDataweb

    ​ 若是很是频繁(毫秒级)的去setData,其致使了两个后果:小程序

    • Android 下用户在滑动时会感受到卡顿,操做反馈延迟严重,由于 JS 线程一直在编译执行渲染,未能及时将用户操做事件传递到逻辑层,逻辑层亦没法及时将操做处理结果及时传递到视图层;
    • 渲染有出现延时,因为 WebViewJS 线程一直处于忙碌状态,逻辑层到页面层的通讯耗时上升,视图层收到的数据消息时距离发出时间已通过去了几百毫秒,渲染的结果并不实时;
  2. 每次setData都传递大量新数据api

    ​ 由setData的底层实现可知,咱们的数据传输实际是一次 evaluateJavascript 脚本过程,当数据量过大 时会增长脚本的编译执行时间,占用 WebView JS 线程。数组

  3. 后台态页面进行 setData缓存

    ​ 当页面进入后台态(用户不可见),不该该继续去进行setData,后台态页面的渲染用户是没法感觉的, 另外后台态页面去setData也会抢占前台页面的执行。性能优化

  4. data中放置大量与界面渲染无关的数据网络

优化方案

1. 减小setdata的数据量app

​ 若是一个数据不会影响渲染层,则不用放在setData里面

2. 合并setdata的请求,减小通信的次数

​ 避免过于频繁调用setData,应考虑将屡次setData合并成一次setData调用

// 不要频繁调用setData
this.setData({ a: 1 })
this.setData({ b: 2 })
// 绝大多数时候可优化为
this.setData({ a: 1, b: 2 })
复制代码

3. 去除没必要要的事件绑定(WXML中的bindcatch),从而减小通讯的数据量和次数

4. 避免在节点的data的前缀属性中防止过大的数据

5. 列表的局部更新

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

  • 能够采用setData全局刷新,点赞完成以后,从新获取数据,再次进行全局从新渲染,这样作的有点是:方便,快捷!缺点是:用户体验极其很差,当用户刷量100多条数据后,从新渲染会出现空白期。
  • 也能够采用局部刷新,将点赞的id传过去,知道点的是哪一条数据,从新获取数据,查找相对应id的那条数据的下标(index是不会改变的),用setData进行局部刷新,如此,即可以显著提高渲染速度。
this.setData({
    list[index]=newList[index]
})
复制代码

6. 与界面渲染无关的数据最好不要放置在data中,能够考虑设置在page对象的其余字段下

Page({
  onShow: function() {
    // 不要设置不在界面渲染时使用的数据,并将界面无关的数据放在data外
    this.setData({
      myData: {
        a: '这个字符串在WXML中用到了',
        b: '这个字符串未在WXML中用到,并且它很长…………………………'
      }
    })
    // 能够优化为
    this.setData({
      'myData.a': '这个字符串在WXML中用到了'
    })
    this._myData = {
      b: '这个字符串未在WXML中用到,并且它很长…………………………'
    }

  }
})
复制代码

7. 防止后台页面的js抢占资源

小程序中可能有n个页面,全部的这些页面,虽然都拥有本身的webview(渲染层),可是却共享同一个js运行环境。也就是说,当你跳到了另一个页面(假设是B页面),本页面(假设是A页面)的定时器等js操做仍在进行,而且不会被销毁,而且会抢占B页面的资源。

img

8. 谨慎使用onPageScroll

pageScoll事件,也是一次通信,是webview层向js逻辑层的通信。此次通信开销较大,若是考虑到这个事件被频繁的调用,回调函数若是有复杂的setData的话性能就会变得不好。

img

9. 尽量使用小程序组件

自定义组件的更新只在组件内部进行,不受页面其余内容的影响,各个组件也将具备各自独立的逻辑空间。每一个组件都分别拥有本身独立的数据,setData调用。

案例分析

检查点:是否频繁去setData

检查结果:暂无

检查点:每次setData都传递大量新数据

检查结果:暂无

检查点:后台态页面进行setData

检查结果:存在

产生缘由:因为改操做处于搜索页面中,在用户点击后便马上返回到上一级页面进行数据展现,故在后台态页面进行setData,提升跳转页面的速度。

问题代码:

// pages/line/searchResult/searchResult.js

showSearchDetail(e){
   ...省略代码
    let prevpage = this.getPrevPage()
        prevpage.setData({
          isInSearch: true,
          showResult,
          keyWord:res.routeName
        })
}
复制代码

检查点:在data中放置大量与界面渲染无关的数据

检查结果:存在

产生缘由:因为当前请求的用以查询线路信息的接口GET/api/Route/List/{cityid}/{pagesize}/{pageno} 不支持分页请求,会一次性返回全部数据,因此在以前的方案中,为了减小请求产生的网络流量,会一次性把全部数据暂存到页面的一个数组中,(该数组存储大概600多个对象),而后再根据需求展现部分数据。

问题代码:

getLinesInformation(cityID) {
  return new Promise((resolve, reject) => {
    smartProxy.getRequest(`/Route/List/${cityID}/10/0`)
      .then(res => {
        this.data.lineArray = res
        if (this.data.lineArray)
          resolve()
        else reject()
      })
  })
},
复制代码

解决方案:

  • 方法一:用流量换性能 不暂存所有线路的信息,改而每次出现分页请求时重复请求该api,更新页面展现所需的数组的数组。

    缺点:重复请求api获取相同的数据,浪费流量

    优化效果:

    在首次跳转搜索页时,耗时500ms,再之后每次跳转搜索页耗时90ms,下拉分页加载平均400ms一页

  • 方法二:改进存储方案 当请求到线路api返回的数据后不放置在data字段,改成设置在page对象的其余字段进行存储。

    优势:减小页面负担,优化性能

    代码实现:

    getLinesInformation(cityID) {
      return new Promise((resolve, reject) => {
        smartProxy.getRequest(`/Route/List/${cityID}/10/0`)
          .then(res => {
            //用lineArray字段存储请求得来的数据
            this.lineArray = res
            if (this.lineArray)
              resolve()
            else reject()
          })
      })
    },
    复制代码

检查点:不当使用onPageScroll

检查结果:存在

产生缘由:本意是为了实现用户在查看线路搜索结果后返回线路展现主页时可以返回到上次浏览位置,因此使用onPageScroll事件获取滚动高度ScrollTop,而后存储。但若是经过onPageScoll事件获取的话至关于每次混动都会触发存储,严重影响页面效果。

问题代码:

onPageScroll: function (e) {
    // 页面滚动时执行
    // console.log(e);
    if (e.scrollTop != 0 && !this.data.isInSearch && !this.data.keyWord) {
      //设置缓存
      wx.setStorage({
        key: 'lineSearchScrollTop',
        // 缓存滑动的距离,和当前页面的id
        data: e.scrollTop
      })
    }
},
复制代码

解决方案:

  • 直接经过wx.createSelectorQuery().selectViewport().scrollOffset获取滚动条高度,而且只在用户点击搜索框跳转到搜索页面的时候才调用,减小onPageScroll事件对页面性能的影响
//获取滚动条高度
  getScrollTop () {
    let that = this
    return new Promise((resolve, rej) => {
      wx.createSelectorQuery().selectViewport().scrollOffset(function (res) {
        that.setData({
          scrollTop: res.scrollTop
        })
        resolve()
      }).exec()
    })
  }
复制代码

Page生命周期

在肯定性能指标前,有必要对小程序页面的生命周期作一个梳理。

在每一个页面注册函数Page()的参数中,有生命周期的方法:onLoadonShowonReadyonHideonUnload

页面触发的第一个生命周期回调是onLoad,在页面加载的时候触发,其参数是页面的query参数,一个页面只有一次;

接着是onShow,监听页面的显示,与onLoad不一样,若是页面被隐藏后再次显示(例如:进入下一页后返回),也会触发该生命周期;

触发onShow以后,逻辑层会向渲染层发送初始化数据,渲染层完成第一次渲染以后,会通知逻辑层触发onReady生命周期,一个页面只有一次;

onHide是页面隐藏但未卸载的时候触发的,如 wx.navigateTo 或底部tab切换到其余页面,小程序切入后台等。

onUnload是页面卸载时触发,如wx.redirectTowx.navigateBack到其余页面时。

img

总体周期

打开页面的状况

首先,前一个页面隐藏,在加载下一个页面以前,须要先初始化新页面的组件。页面首次渲染以后,会触发组件的ready,最后触发的是页面的onReady,以下图:

img

从PageA打开pageB时的生命周期顺序

离开页面的状况

离开当前页面时,首先触发当前页面的卸载onUnload,接着是组件离开节点树的detached。最后显示以前的页面,触发onShow。以下图:

img

从PageB返回到PageA的生命周期顺序

切换到后台

切换到后台时,小程序和页面并无卸载,只会触发隐藏。先触发页面的onHide,接着是App的onHide。以下图:

img

切换到后台时的生命周期顺序

切换到前台

切换到后台时,小程序会先触发onShow,以后才是页面的onShow。以下图:

img

切换到前台时的生命周期顺序

关键性能指标

了解了小程序各个阶段的生命周期,咱们能够制定出关键节点的性能指标,整理以下表:

img

记录数据

若是咱们记录下每个页面的优化先后的可交互时间数据,而且对比,能够很好的分析每个页面的性能提高有多少,从而判断本身有没有在作无用功。

从上面的关键性能指标中,抽取可交互时间做为本次的重要评估指标之一,即从小程序页面onload事件算起,页面发起异步请求,请求回来后,把数据经过setData 渲染到页面后,上述一整个流程所花费的时间。

可是,一个小程序项目每每会有不少个页面,手动记录每个小程序的首屏时间,很麻烦。

所以,咱们能够改写this.setData方法,加入上报时间点逻辑。

this._startTime = new Date().getTime();
let fn = this.setData;
this.setData = (obj = {}, handle = '') => {
	let now = new Date().getTime();
    // 上报渲染所须要的时间
	log(now - this._startTime)
	fn.apply(this, [obj, handle]);
};
复制代码

另外,还有一些记录性能指标须要记录,在本次的班车线路展现页面中,当用户下拉触底时的分页加载时间和从点击搜索框到搜索页面加载出来的时间也是咱们本次重要的性能评估标准。对于这种自定义场景,咱们能够利用console.time()console.timeEnd()这一对函数来记录。

指标测试

测试平台:小米8 SE、小程序开发工具

测试流程:首页 -> 线路 -> 下拉触底 -> 点击搜索框

测试指标:可交互时间,分页加载时间、页面跳转时间

优化后指标:

平台 可交互时间(ms) 分页加载时间(ms) 页面跳转时间(ms)
小程序开发工具 400 130 180
小米8 SE(扫二维码真机调试模式) 3000 110 1000

其中,扫二维码真机调试模式因为其自己问题,时间变长为正常现象,在自动真机调试模式中,各项指标恢复正常,但因为没有确切数据,故不列入表格中。

优化心得

自此,线路展现页面的性能优化完成,在实际优化过程当中,发现性能影响最大的就是下面的问题

  • 下拉加载更多,特别特别卡,开始觉得是由于随着下拉page维护的数据愈来愈多致使,在减小了page实例中维护的数据后,发现性能改善不大。 后来发现,是由于须要监听scroll事件,致使scroll事件被频繁的触发,回调函数中有耗时操做,致使onreachBottom事件被阻塞了,也就是说,要等大概1~2秒才会去发起下一页的请求。 取消掉scroll事件的监听,性能就大大提高了。归根结底仍是对小程序的api不熟悉,为了得到滚动条高度而频繁监听scroll事件,可谓是本末倒置。
相关文章
相关标签/搜索