1.如何实现上拉加载分页列表的性能优化
我们的功能里面有个滚动到底部加载的功能,优化前我们的做法是这样的:
大部分人面对长列表滚动的时候,一开始的处理方式都是这样的,如果数据不多,只有几页可能不会太暴露问题,如果页数过多,几十页甚至上百页的情况,list的数据会越来越大,每次setData的数据就会越来越多,因而每次页面重新渲染的节点就会越来越多,从而导致滚动到后面,加载越来越慢。
另外,由于小程序的视图渲染层和数据逻辑处理层是分开的,不是在同一个线程上面的,从用户触发页面交互,到处理数据逻辑,最后层现页面,数据到视图是需要传输的,因而小程序本身对数据大小也有限制,不能超过1M。
// 只阐述逻辑,非真实代码
// 1: 初始一个list,存储列表数据
data = startList
// 2: 监听滚动事件,滚动到底部获取新数据,并追加到list尾部,最后重新setData
onReachBottom:()=>{
const {list} = this.datajs
fetchNewData().then((res)=>{
list.push(res.list);
this.setData({list}) // 数据更新操作(注意的重点)
}
}
利用setData数据路径优化:可以通过数据路径的写法来将数据分批的传输到视图层中,减少一次性setData的数据大小。具体写法如下:
// 1.通过一个二维数组来存储数据
let feedList = [[array]];
// 2.维护一个页面变量值,加载完一次数据page++
let page = 1
// 3.页面每次滚动到底部,通过数据路径更新数据
onReachBottom:()=>{
fetchNewData().then((newVal)=>{
// 利用数据路径分批设置数据进行传递
this.setData({
['feedList[' + (page - 1) + ']']: newVal,
})
}
}
// 4.最终我们的数据是[[array1],[array2]]这样的格式,然后通过wx:for遍历渲染数据
2.存在短时间内发起太多图片请求的优化(懒加载)
就是渲染页面时,一次性发送了过多的图片请求,导致了同一时间发起了过多的http请求,http连接是非常耗时的,尤其是一次性发起这么多,并且一次性发起的http链接也是有限制的,比如chrome浏览器就限制一次性最多6个。
所以在渲染页面时,不在视图范围内的图片我们不加载,只有元素出现在视图范围内了,再渲染。
常规的做法是,通过 getBoundingClientRect() 获取元素的位置,然后与页面滚动位置比较,如果出现在视图内,就将 img 显示。这种方式有2个问题
getBoundingClientRect()方法调用本身容易引起页面重排
监听滚动事件本身就频繁触发,虽然可以通过节流的方式来减少,但还是容易增加无谓代码处理
3.存在图片太大而显示区域过小
这个问题就是指图片尺寸太大了,而页面上我们显示的尺寸又太小了,图片尺寸大,请求图片就越慢,导致页面渲染速度下降。
对于页面里面的图片,最好都把图片存储在cdn服务器上,一个是能充分利用cdn缓存来加快请求速度,另外一个就是cdn上能够将图片进行一定的处理,比如裁剪。就是通过cdn来响应图片处理,然后请求图片时告诉cdn服务器需要什么要的尺寸图片,由cdn服务器响应对应尺寸图片。
比如阿里云oss图片缩放:https://help.aliyun.com/document_detail/44688.html?spm=5176.13910061.sslink.1.1a38601648eLCx
4.利用key实现列表性能的提升
key值在列表渲染的时候,能够提升列表渲染性能,为什么呢?首先得想想小程序的页面是如何渲染的,主要分为以下几步:
将wxml结构的文档构建成一个vdom虚拟数
页面有新的交互,产生新的vdom数,然后与旧数进行比较,看哪里有变化了,做对应的修改(删除、移动、更新值)等操作(对比vue、react)
最后再将vdom渲染成真实的页面结构
key值的作用就在第二步,当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率。key值如果不指明,默认会按数组的索引来处理,因而会导致一些类似input等输入框组件的值出现混乱的问题。
不加key,在数组末尾追加元素,之前已渲染的元素不会重新渲染。但如果是在头部或者中间插入元素,整个list被删除重新渲染,且input组件的值还出现了混乱,值没有正常被更新
添加key,在数组末尾、中间、或者头部插入元素,其它已存在的元素都不会被重新渲染,值也能正常被更新
因而,在做list渲染时,如果list的顺序发生变化时,最好增加key,且不要简单的使用数组索引当做key,需要用唯一值当成key。
5.图片资源优化
使用 WebP 格式,WebP 是 Google 推出的一种支持有损/无损压缩的图片文件格式,得益于更优的图像数据压缩算法,其与 JPG、PNG 等格式相比,在肉眼无差别的图片质量前提下具有更小的图片体积(据官方说明,WebP 无损压缩体积比 PNG 小 26%,有损压缩体积比 JPEG 小 25-34%)
需注意android设备支持webp格式,但ios设备不支持webp格式
6.骨架屏的应用
可以从降低网络请求时延、减少关键渲染的节点数这两个角度出发,缩短完成 FMP(首次有效绘制)的时间。
骨架屏的应用从用户感知的角度可以优化加载体验。
7.利用事件委托减少子元素事件绑定
列表循环的时候,每个子元素如果都需要进行事件绑定将会损耗大量的属性设置以及事件绑定操作,降低应用的性能
可以将事件委托于父元素,这样就只需要进行一个事件对象的绑定操作即可,性能可以得到极大的提升
而且子元素不管是固定还是动态追加,都可以利用事件冒泡触发委托于父元素的事件回调函数
8.利用防抖与节流进行性能优化
在屏幕滚动与拖拽的时候,经常会用到一些持续触发的事件,而这类事件不可控触发频率非常高,大大影响了性能,而我们想要让其变得可控,就可以用到节流和防抖两种方案。
防抖:指的是事件在高频触发状态时,只收集事件最后一次执行的结果。例如滚动时候产生的scrollTop,只收集停止滚动时候的scrollTop,实现原理是每次滚动触发时,都清除定时器队列,只执行最后一个定义的定时器。
// 由于作用域的原因,let timer必须放在页面构造器Page外边,否则无法清除定时器队列,导致重复触发!
let timer;
Page({
debounce(e){
clearTimeout(timer);
timer = setTimeout(() => {
console.log(e.detail.scrollTop)
}, 500);
}
})
节流:指的是降低事件的触发频率,周期性获取事件的执行结果,例如滚动1像素便会触发的滚动事件,我们可以让它周期性每隔一段时间执行一次。实现原理是下一个定时器任务必须等待当前定时器任务执行完才执行。
Page({
data: {
lock: true
},
throttling(e){
let timer
if(this.data.lock){
this.setData({
lock: false
})
setTimeout(()=>{
this.setData({
lock: true
})
console.log(e.detail.scrollTop)
},500)
}
}
})
9.利用事件总线进行数据传递
利用事件总线替代组件间数据绑定的通信方式,WXML 数据绑定是小程序中父组件向子组件传递动态数据的较为常见的方式,在此过程中,不可避免地需要经历一次组件的 setData 调用方可完成任务,这就会产生线程间的通信。通过事件总线(EventBus),也就是发布/订阅模式,来完成由父向子的数据传递。子组件被创建时事先监听数据下发事件,当父组件获取到数据后触发事件把数据传递给子组件,这整个过程都是在小程序的逻辑层里同步执行,比数据绑定的方式速度更快。
不过需要注意事件发布与订阅的次数的合理性问题,如果在不合适的位置进行订阅操作可能会出现多次重复订阅同一事件问题
在合适的位置进行订阅取消操作,防止无效订阅操作
10.接口聚合,减少请求数
小程序request请求最大并发数为10个,需要合理控制最大并发数
超出并发限制数目的 HTTP 请求将会被阻塞,需要在队列中等待前面的请求完成,从而一定程度上增加了请求时延。
对于职责类似的网络请求,最好采用节流的方式,先在一定时间间隔内收集数据,再合并到一个请求体中发送给服务端,以便实现请求合并操作
11.视频播放列表如何实现性能优化
视频列表多个video视频播放器性能极差(视频播放器本身占用资源大)
优化方案:
将列表中的视频组件更换成image组件,列表中没有视频组件
要对某个视频进行播放,选中时再将image替换成video,这样列表中的视频组件有且只有一个,可以极大提升列表及播放器等性能