前言
项目优化这个话题已经讨论到烂大街了,但是我们日常中vue,react这些项目都是依托于webpack,grunt,gulb等常见的工具类进行项目的构建,打包,所以我们只需要对这些工具进行一些配置来优化即可,但是对于小程序(本文指微信小程序)是依托于微信api的,那么我们就来讨论一下如何针对小程序进行优化!
常见的性能问题
1.首屏时间 首屏时间是指用户从打开小程序看到第一屏主要内容的时间,首屏时间太长会导致用户长时间看到的都是白屏,影响使用体验
2.渲染时间 渲染时间指的是首次渲染或因数据变化带来的页面结构变化的渲染花费的时间。
3.脚本执行时间 脚本执行时间是指JS脚本在一次同步执行中消耗的时间,比如生命周期回调、事件处理函数的同步执行时间。
4.setData调用频率 setData接口的调用涉及逻辑层与渲染层间的线程通信,通信过于频繁可能导致处理队列阻塞,界面渲染不及时而导致卡顿,应避免无用的频繁调用。
5.setData数据大小 由于小程序运行逻辑线程与渲染线程之上,setData的调用会把数据从逻辑层传到渲染层,数据太大会增加通信时间。
6.避免setData数据冗余 setData操作会引起框架处理一些渲染界面相关的工作,一个未绑定的变量意味着与界面渲染无关,传入setData会造成不必要的性能消耗。
这几个都是项目中常见的性能问题,特别是第一点和第二点都是最直观地让用户吐槽的地方,那么下面我们一起来针对这些问题进行优化!
首屏加载的优化
提前请求
异步请求可以在页面onLoad就加载,不需要等页面ready后在异步请求数据;当然,如果能在前置页面点击跳转时预请求当前页的核心异步请求,效果会更好;
利用缓存
利用storage API, 对变动频率比较低的异步数据进行缓存,二次启动时,先利用缓存数据进行初始化渲染,然后后台进行异步数据的更新,这不仅优化了性能,在无网环境下,用户也能很顺畅的使用到关键服务;
避免白屏
可以在前置页面将一些有用的字段带到当前页,进行首次渲染(列表页的某些数据–> 详情页),没有数据的模块可以进行骨架屏的占位,使用户不会等待的很焦虑,甚至走了;
及时反馈
及时的对需要用户等待的交互操作进行反馈,避免用户以为小程序卡了,无响应
渲染性能优化
想要对渲染进行性能优化,那么我们要想知道渲染过程中做了些什么?
小程序渲染原理
双线程下的界面渲染,小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面。
分析这个流程不难得知:页面初始化的时间大致由页面初始数据通信时间和初始渲染时间两部分构成。其中,数据通信的时间指数据从逻辑层开始组织数据到视图层完全接收完毕的时间,数据量小于64KB时总时长可以控制在30ms内。传输时间与数据量大体上呈现正相关关系,传输过大的数据将使这一时间显著增加。因而减少传输数据量是降低数据传输时间的有效方式。
从减少传输数据量这方面我们可以知道主要是数据层对渲染的性能进行了消耗,所以最主要的优化对象就是数据处理层,最常见的就是setData,我们来看setData到底做了些什么:
setData工作原理
小程序分为逻辑层和渲染层,而我们每次逻辑层改变了,要借由 Native 进行。小程序的渲染层和逻辑层由两个线程管理:渲染层的界面使用了 WebView 进行渲染;逻辑层采用 JsCore 线程运行 JS 脚本。一个小程序存在多个界面,所以渲染层存在多个 WebView 线程,这两个线程的通信会经由微信客户端 Native 做中转,逻辑层发送网络请求也经由 Native 转发,请看下图:所以我们不要重复 setdata ,以及减少数据的传输量。我们的数据传输实际是一次 Javascript 脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程。去除不必要的事件绑定( WXML 中的 bind 和 catch ),从而减少通信的数据量和次数。
因此我们应该避免频繁调用setData
setData接口的调用涉及逻辑层与渲染层间的线程通过,通信过于频繁可能导致处理队列阻塞,界面渲染不及时而导致卡顿,应避免无用的频繁调用
常见的 setData 操作错误:
频繁的去 setData
Android 下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时
每次 setData 都传递大量新数据
由setData的底层实现可知,数据传输实际是一次 evaluateJavascript 脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程
所以建议减少setData的使用,具体方法可以考虑将多次setData合并成一次setData调用;与界面渲染无关的数据最好不要设置在data中,可以考虑设置在page对象的其他字段下
提升数据更新性能方式的代码示例
Page({ onShow: function() { // 不要频繁调用setData this.setData({ a: 1 }) this.setData({ b: 2 }) // 绝大多数时候可优化为 this.setData({ a: 1, b: 2 }) // 不要设置不在界面渲染时使用的数据,并将界面无关的数据放在data外 this.setData({ myData: { a: '这个字符串在WXML中用到了', b: '这个字符串未在WXML中用到,而且它很长…………………………' } }) // 可以优化为 this.setData({ 'myData.a': '这个字符串在WXML中用到了' }) this._myData = { b: '这个字符串未在WXML中用到,而且它很长…………………………' } }})
避免过大的 WXML 节点数目
建议一个页面使用少于 1000 个 WXML 节点,节点树深度少于 30 层,子节点数不大于 60 个。一个太大的 WXML 节点树会增加内存的使用,样式重排时间也会更长
避免将不可能被访问到的页面打包在小程序包里
小程序的包大小会影响加载时间,应该尽量控制包体积大小,避免将不会被使用的文件打包进去
尽可能使用小程序组件
自定义组件的更新只在组件内部进行,不受页面其他不能分内容的影响;比如一些运营活动的定时模块可以单独抽出来,做成一个定时组件,定时组件的更新并不会影响页面上其他元素的更新;各个组件也将具有各自独立的逻辑空间。每个组件都分别拥有自己的独立的数据、setData调用
分包处理
受限于小程序的主包不能超过 2M ,所以我们只能将部分页放置于分包中,下面是分包设置示例:
project|────src | Thanks | pages | components | styles └ utils | pages | utils | styles | components | app.js └ APP.vue└───project.config.json
app.js的配置
config:{ pages:[ "pages/home/index", // 首页 ], subPackages: [ { root: "Thanks", pages: [ "pages/home/index",// 首页 ], independent: true } ],}
分包预加载
可以通过分包预加载的方案,来解决这个问题。打开首页,加载完主包后,可以静默加载其他分包,通过配置 preloadRule 即可实现分包预加载。
"preloadRule":{ "pages/index":{ "network": "all", "packages": ["xxxx"] }}
总结
小程序启动加载性能
1.控制代码包的大小
2.分包加载
3.首屏体验(预请求,利用缓存,避免白屏)
小程序渲染性能
1.避免不当的使用setData
2.合理利用事件通信
3.避免不当的使用onPageScroll
4.优化视图节点
5.使用自定义组件
- END -
结伴同行前端路