mpvue性能优化实战技巧(小结)


Posted in Javascript onApril 17, 2019

最近一直在折腾mpvue写的微信小程序的性能优化,分享下实战的过程。

先上个优化前后的图:

mpvue性能优化实战技巧(小结)

可以看到打包后的代码量从813KB减少到387KB,Audits体验评分从BA,效果还是比较明显的。其实这个指标说明不了什么,而且轻易就可以做到,更重要的是优化小程序运行过程中的卡顿感,请耐心往下看。

常规优化

常规的Web端优化方法在小程序中也是适用的,而且不可忽视。

一、压缩图片

这一步最简单,但是容易被忽视。在tiny上在线压缩,然后下载替换即可。

mpvue性能优化实战技巧(小结)

我这项目的压缩率高达72%,可以说打包后的代码从813KB降到387KB大部分都是归功于压缩图片了。

二、移除无用的库

我之前在项目中使用了Vant Weapp,在static目录下引入了整个库,但实际上我只使用了button,field,dialog等几个组件,实在是没必要。

所以干脆移除掉了,微信小程序自身提供的buttonwx.showModal等一些组件基本可以满足需求,自己手写一下样式也不用花什么时间。

在这里建议大家,在微信小程序中,尽量避免使用过多的依赖库

不要贪图方便而引入一些比较大的库,小程序不同于Web,限制比较多,能自己写一下就尽量自己写一下吧。

小程序的优化

咱们首先得看一下官方优化建议,大多是围绕这个建议去做。

一、开启Vue.config._mpTrace = true

这个是mpvue性能优化的一个黑科技啊,可能大多数同学都不知道这个,我在官方文档都没有搜到到这个配置,我真的是服了。

我能找到这个配置也是Google机缘巧合下看到的,出处:mpvue重要更新,页面更新机制进行全面升级
具体做法是在/src/main.js添加Vue.config._mpTrace = true,如:

Vue.config._mpTrace = true
Vue.config.productionTip = false
App.mpType = 'app'

添加了Vue.config._mpTrace属性,这样就可以看到console里会打印每500ms更新的数据量。如图:

mpvue性能优化实战技巧(小结)

如果数据更新量很大,会明显感觉小程序运行卡顿,反之就流畅。因此我们可以根据这个指标,逐步找出性能瓶颈并解决掉。

二、精简data

1. 过滤api返回的冗余数据

后端的api可能是需要同时为iOS,Android,H5等提供服务的,往往会有些冗余的数据小程序是用不到的。比如api返回的一个文章列表数据有很多字段:

this.articleList = [
  {
    articleId: 1,
    desc: 'xxxxxx',
    author: 'fengxianqi',
    time: 'xxx',
    comments: [
      {
        userId: 2,
        conent: 'xxx'
      }
    ]
  },
  {
    articleId: 2
    // ...
  },
  // ...
]

假设我们在小程序中只需要用到列表中的部分字段,如果不对数据做处理,将整个articleListsetData进去,是不明智的。

小程序官方文档:单次设置的数据不能超过1024kB,请尽量避免一次设置过多的数据。

可以看出,内存是很宝贵的,当articleList数据量非常大超过1M时,某些机型就会爆掉(我在iOS中遇到过很多次)。

因此,需要将接口返回的数据剔除掉不需要的,再setData,回到我们上面的articleList例子,假设我们只需要用articleIdauthor这两个字段,可以这样:

import { getArticleList } from '@/api/article'
export default {
  data () {
    return {
      articleList: []
    }
  }
  methods: {
    getList () {
      getArticleList().then(res => {
        let rawList = res.list
        this.articleList = this.simplifyArticleList(rawList)
      })
    },
    simplifyArticleList (list) {
      return list.map(item => {
        return {
          articleId: item.articleId,
          author: item.author
          // 需要哪些字段就加上哪些字段
        }
      })
    }
  }
}

这里我们将返回的数据通过simplifyArticleList 来精简数据,此时过滤后的articleList中的数据类似:

[
  {articleId: 1, author: 'fengxianqi'},
  {articleId: 2, author: 'others'}
  // ...
]

当然,如果你的需求中是所有数据都要用到(或者大部分数据),就没必要做一层精简了,收益不大。毕竟精简数据的函数中具体的字段,是会增加维护成本的。

PS: 在我个人的实际操作中,做数据过滤虽然增加了维护的成本,但一般收益都很大,因次这个方法比较推荐。

2. data()中只放需要的数据

import xx from 'xx.js'
export default {
  data () {
    return {
      xx,
      otherXX: '2'
    }
  }
}

有些同学可能会习惯将import的东西都先放进data中,再在methods中使用,在小程序中可能是个不好的习惯。

因为通过Vue.config._mpTrace = true在更新某个数据时,我对比放进data和不放进data中的两种情况会有差别。

所以我猜测可能是data是会一起更新的,比如只是想更新otherXX时,会同时将xx也一起合起来setData了。

3. 静态图片放进static

这个问题和上面的问题其实是一样的,有时候我们会通过import的方式引入,比如这样:

<template>
  <img :src="UserIcon">
</template>
<script>
import UserIcon from '@/assets/images/user_icon.png'
export default {
  data () {
    return {
      UserIcon
    }
  }
}
</script>

这样会导致打包后的代码,图片是base64形式(很长的一段字符串)存放在data中,不利于精简data。同时当该组件多个地方使用时,每个组件实例都会携带这一段很长的base64代码,进一步导致数据的冗余。

因此,建议将静态图片放到static目录下,这样引用:

<template>
  <img src="/static/images/user_icon.png">
</template>

代码也更简洁清爽。

看一下做了上面操作的前后对比图,使用体验上也流畅了很多。

mpvue性能优化实战技巧(小结)

三、swiper优化

小程序自身提供的swiper组件性能上不是很好,使用时要注意。参考着两个思路:

一般情况下,推荐使用后者。我在项目中尝试使用了前者,同样能达到很好的效果,请继续看下面的分享。

五、善用storage

1.为什么说要善用storage

由于小程序的内存非常宝贵,占用内存过大会非常卡顿,因此最好尽可能少的将数据放到内存中,即vuex存的数据要尽可能少。而小程序的storage支持单个 key允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB

所以可以将一些相对取用不频繁的数据放进storage中,需要时再将这些数据放进内存,从而缓解内存的紧张,有点类似Windows中虚拟内存的概念。

2.storage换内存的实例

这个例子讲的会有点??拢?嬲?苡玫降呐笥芽梢韵晗缚聪隆?/blockquote>

上面讲到playList数据量太多,播放一条音频时其实只需要最多保证3条数据在内存中即可,即上一首播放中的下一首,我们可以将多余的播放列表存放在storage中。

PS: 为了保证更平滑地连续切换下一首,我们可以稍微保存多几条,比如我这里选择保存5条数据在vuex中,播放时始终保证当前播放的音频前后都有两条数据。
// 首次播放背景音频的方法
async function playAudio (audioId) {
  // 拿到播放列表,此时的playList最多只有5条数据。getPlayList方法看下面
  const playList = await getPlayList(audioId)
  // 当前音频在vuex中的currentIndex
  const currentIndex = playList.findIndex(item => item.audioId === audioId)
  
  // 播放背景音频
  this.audio = wx.getBackgroundAudioManager()
  this.audio.title = playList[currentIndex].title
  this.audio.src = playList[currentIndex].url
  
  // 通过mapActions将播放列表和currentIndex更新到vuex中
  this.updateCurrentIndex(index) 
  this.updatePlayList(playList) 
  // updateCurrentIndex和updatePlayList是vuex写好的方法
}

// 播放音频时获取播放列表的方法,将所有数据存在storage,然后返回当前音频的前后2条数据,保证最多5条数据
import { loadPlayList } from '@/api/audio'
async function getPlayList (courseId, currentAudioId) {
  // 从api中请求得到播放列表
  // loadPlayList是api的方法, courseId是获取列表的参数,表示当前课程下的播放列表
  let rawList = await loadPlayList(courseId)
  // simplifyPlayList过滤掉一些字段
  const list = this.simplifyPlayList(rawList)
  // 将列表存到storage中
  wx.setStorage({
    key: 'playList',
    data: list
  })
  return subPlayList(list, currentAudioId)
}

重点是subPlayList方法,这个方法保证了拿到的播放列表是最多5条数据。

function subPlayList(playList, currentAudioId) {
 let tempArr = [...playList]
 const count = 5 // 保持vuex中最多5条数据
 const middle = parseInt(count / 2) // 中点的索引
 const len = tempArr.length
 // 如果整个原始的播放列表本来就少于5条数据,说明不需要裁剪,直接返回
 if (len <= count) {
  return tempArr
 }
 // 找到当前要播放的音频的所在位置
 const index = tempArr.findIndex(item => item.audioId === currentAudioId)
 // 截取当前音频的前后两条数据
 tempArr = tempArr.splice(Math.max(0, Math.min(len - count, index - middle)), count)
 return tempArr
}

tempArr.splice(Math.max(0, index - middle), count)可能有些同学比较难理解,需要仔细琢磨一下。假设playList有10条数据:

  • 当前音频是列表中的第1条(索引是0),截取前5个:playList.splice(0, 5),此时currentAudio在这5个数据的索引是0,没有上一首,有4个下一首
  • 当前音频是列表中的第2条(索引是1),截取前5个:playList.splice(0, 5),此时currentAudio在这5个数据的索引是1,有1个上一首,3个下一首
  • 当前音频是列表中的第3条(索引是2),截取前5个:playList.splice(0, 5),此时currentAudio在这5个数据的索引是2,有2个上一首,2个下一首
  • 当前音频是列表中的第4条(索引是3),截取第1到6个:playList.splice(1, 5)
  • ,此时currentAudio在这5个数据的索引是2,有2个上一首,2个下一首
  • 当前音频是列表中的第5条(索引是4),截取第2到7个:playList.splice(2, 5),此时currentAudio在这5个数据的索引是2,有2个上一首,2个下一首
  • ...
  • 当前音频是列表中的第9条(索引是8),截取后5个:playList.splice(4, 5),此时currentAudio在这5个数据的索引是3,有3个上一首,1个下一首
  • 当前音频是列表中的最后1条(索引是9),截取后的5个:playList.splice(4, 5),此时currentAudio在这5个数据的索引是4,有4个上一首,没有下一首

有点??拢?行巳さ耐?ё邢缸聊ハ拢?蘼鄣鼻耙羝翟谀模?际贾毡Vち四玫降鼻耙羝登昂蟮淖疃?条数据。

接下来就是维护播放上一首或下一首时保证当前vuex中的playList始终是包含当前音频的前后2条。

播放下一首

function playNextAudio() {
  const nextIndex = this.currentIndex + 1
  if (nextIndex < this.playList.length) {
    // 没有超出数组长度,说明在vuex的列表中,可以直接播放
    this.audio = wx.getBackgroundAudioManager()
    this.audio.src = this.playList[nextIndex].url
    this.audio.title = this.playList[nextIndex].title
    this.updateCurrentIndex(nextIndex)
    // 当判断到已经到vuex的playList的边界了,重新从storage中拿数据补充到playList
    if (nextIndex === this.playList.length - 1 || nextIndex === 0) {
     // 拿到只有当前音频前后最多5条数据的列表
     const newList = getPlayList(this.playList[nextIndex].courseId, this.playList[nextIndex].audioId)
     // 当前音频在这5条数据中的索引
     const index = newList.findIndex(item => item.audioId === this.playList[nextIndex].audioId)
     // 更新到vuex
     this.updateCurrentIndex(index)
     this.updatePlayList(newList)
    }
  }
}

这里的getPlayList方法是上面讲过的,本来是从api中直接获取的,为了避免每次都从api直接获取,所以需要改一下,先读storage,若无则从api获取:

import { loadPlayList } from '@/api/audio'
async function getPlayList (courseId, currentAudioId) {
  // 先从缓存列表中拿
  const playList = wx.getStorageSync('playList')
  if (playList && playList.length > 0 && courseId === playList[0].courseId) {
   // 命中缓存,则从直接返回
   return subPlayList(playList, currentAudioId)
  } else {
   // 没有命中缓存,则从api中获取
   const list = await loadPlayList(courseId)
   wx.setStorage({
    key: 'playList',
    data: list
   })
   return subPlayList(list, currentAudioId)
  }
}

播放上一首也是同理,就不赘述了。

PS: 将vuex中的数据精简后,我所做的小程序在播放音频时刷其他页面已经非常流畅啦,效果非常好。

六、动画优化

这个问题在mpvue开发音频类小程序踩坑和建议已经讲过了,感兴趣的可以移步看一眼,这里只写下概述:

如果要使用动画,尽量用css动画代替wx.createAnimation使用css动画时建议开启硬件加速最后

大致总结一下上面所讲的几个要点:

  • 开发时打开Vue.config._mpTrace = true。
  • 谨慎引入第三方库,权衡收益。
  • 添加数据到data中时要克制,能精简尽量精简。
  • 图片记得要压缩,图片在显示时才渲染。
  • vuex保持数据精简,必要时可先存storage。

性能优化是一个永不止步的话题,我也还在摸索,不足之处还请大家指点和分享。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
初窥JQuery(一)jquery选择符 必备知识点
Nov 25 Javascript
Moment.js 不容错过的超棒Javascript日期处理类库
Apr 15 Javascript
用js的for循环获取radio选中的值
Oct 21 Javascript
js函数名与form表单元素同名冲突的问题
Mar 07 Javascript
jQuery基础知识小结
Dec 22 Javascript
常用的jQuery前端技巧收集
Dec 24 Javascript
修复bash漏洞的shell脚本分享
Dec 31 Javascript
jQuery插件expander实现图片翻转特效
May 21 Javascript
jQuery实用技巧必备(下)
Nov 03 Javascript
js实现日期显示的一些操作(实例讲解)
Jul 27 Javascript
JS中判断字符串存在和非空的方法
Sep 12 Javascript
使用VueRouter的addRoutes方法实现动态添加用户的权限路由
Jun 03 Javascript
node.js监听文件变化的实现方法
Apr 17 #Javascript
vue中格式化时间过滤器代码实例
Apr 17 #Javascript
postman自定义函数实现 时间函数的思路详解
Apr 17 #Javascript
vue指令之表单控件绑定v-model v-model与v-bind结合使用
Apr 17 #Javascript
记一次Vue.js混入mixin的使用(分权限管理页面)
Apr 17 #Javascript
详解js获取video任意时间的画面截图
Apr 17 #Javascript
解决vue跨域axios异步通信问题
Apr 17 #Javascript
You might like
TP5框架实现自定义分页样式的方法示例
2020/04/05 PHP
Laravel 5+ .env环境配置文件详解
2020/04/06 PHP
jQuery getJSON 处理json数据的代码
2010/07/26 Javascript
改进UCHOME的记录发布,增强可访问性用户体验
2011/01/17 Javascript
用js判断页面是否加载完成实现代码
2012/12/11 Javascript
javascript延时加载之defer测试
2012/12/28 Javascript
终于解决了IE8不支持数组的indexOf方法
2013/04/03 Javascript
jQuery实现可收缩展开的级联菜单实例代码
2013/11/27 Javascript
Js实现网页键盘控制翻页的方法
2014/10/30 Javascript
详解JavaScript中双等号引起的隐性类型转换
2016/05/30 Javascript
JavaScript中ES6字符串扩展方法
2016/08/26 Javascript
jQuery中select与datalist制作下拉菜单时的区别浅析
2016/12/30 Javascript
video.js使用改变ui过程
2017/03/05 Javascript
小试SVG之新手小白入门教程
2019/01/08 Javascript
Vue实现一个图片懒加载插件
2019/03/11 Javascript
JS中作用域以及变量范围分析
2020/07/18 Javascript
antd vue table跨行合并单元格,并且自定义内容实例
2020/10/28 Javascript
Map与WeakMap类型在JavaScript中的使用详解
2020/11/18 Javascript
[02:30]辉夜杯主赛事第二日胜者组半决赛 CDEC.Y赛后采访
2015/12/26 DOTA
Python数据结构之顺序表的实现代码示例
2017/11/15 Python
PyCharm鼠标右键不显示Run unittest的解决方法
2018/11/30 Python
python之Flask实现简单登录功能的示例代码
2018/12/24 Python
Python从函数参数类型引出元组实例分析
2019/05/28 Python
python实现五子棋小程序
2019/06/18 Python
Django后端发送小程序微信模板消息示例(服务通知)
2019/12/17 Python
交通专业个人自荐信格式
2013/09/23 职场文书
大学生实习期自我评价范文
2013/10/03 职场文书
关于打架的检讨书
2014/01/17 职场文书
小学毕业感言150字
2014/02/05 职场文书
毕业生就业意向书
2014/04/01 职场文书
劳资协议书范本
2014/04/23 职场文书
学雷锋标兵事迹材料
2014/08/18 职场文书
做一个有道德的人活动实施方案
2014/08/23 职场文书
惊涛骇浪观后感
2015/06/05 职场文书
继承Win10缺点!教你关闭Win11烦人的网络搜索
2021/11/23 数码科技
SpringBoot详解整合Redis缓存方法
2022/07/15 Java/Android