小程序性能深度优化实战
小程序性能优化深度
1. setData 性能优化
为什么 setData 影响性能?
- 逃逸层线程不能直接操作 DOM
- setData 数据经过序列化 -> Native 通信 -> 渲染层更新
- 数据量大、调用频繁就会成为瓶颈
优化实践
// 1. 只传必要的字段
const { list, count } = this.data
// bad
this.setData({ list: newList }) // 传辘1000条数据
// good
this.setData({ 'list[5].status': 'done' }) // 只传变化的字段
// 2. 减少 setData 次数(批量合并)
// bad
this.setData({ count: 1 })
this.setData({ loading: false })
// good
this.setData({ count: 1, loading: false })
// 3. 防抖高频调用(scroll 事件)
let timer
onScroll(e) {
clearTimeout(timer)
timer = setTimeout(() => {
this.setData({ scrollTop: e.detail.scrollTop })
}, 100)
}
// 4. 分屏渲染(分批设置大列表)
async function renderBatch(list) {
const BATCH = 20
for (let i = 0; i < list.length; i += BATCH) {
this.setData({ [`list`]: list.slice(0, i + BATCH) })
await new Promise(r => setTimeout(r, 16)) // 一帧的时间
}
}
2. 页面渲染优化
骨架屏
<!-- 骨架屏:首屏删叨剩留作平衡 -->
<view wx:if="{{loaded}}">
<real-content />
</view>
<view wx:else class="skeleton">
<view class="skeleton-avatar" />
<view class="skeleton-line" />
<view class="skeleton-line short" />
</view>
图片懒加载
<!-- 小程序内置支持 -->
<image lazy-load="{{true}}" src="{{item.url}}" />
<!-- 街道模式:Intersection Observer -->
// IntersectionObserver 实现懒加载
Page({
onReady() {
const observer = this.createIntersectionObserver()
observer.relativeToViewport({ bottom: 100 })
observer.observe('.load-more', (res) => {
if (res.intersectionRatio > 0) {
this.loadMore()
}
})
}
})
3. 分包策略深度
主包优化策略
{
"pages": [
"pages/index/index",
"pages/home/home"
],
"subpackages": [
{
"root": "packageUser",
"name": "user",
"pages": ["pages/profile", "pages/settings"]
},
{
"root": "packageOrder",
"name": "order",
"pages": ["pages/list", "pages/detail"]
}
],
"preloadRule": {
"pages/index/index": {
"network": "wifi",
"packages": ["user"]
}
}
}
分包最佳实践
- 主包只放起始页和公用组件、工具库
- 按功能模块划分分包
preloadRule配置分包预加载- 独立分包用于分享页、广告部置
4. 内存与渲染性能
内存优化
// 1. 图片内存管理——避免重复创建
const imageCache = new Map()
function getImage(url) {
if (!imageCache.has(url)) {
imageCache.set(url, wx.createImage ? wx.createImage() : null)
}
return imageCache.get(url)
}
// 2. 组件销毁时清理引用
Component({
lifetimes: {
detached() {
this._timer && clearInterval(this._timer)
this._observer && this._observer.disconnect()
}
}
})
// 3. 大数据分页加载,不要一次性读取全量
Page({
data: { list: [], page: 1, hasMore: true },
async loadMore() {
if (!this.data.hasMore) return
const res = await api.getList({ page: this.data.page })
this.setData({
list: [...this.data.list, ...res.data],
page: this.data.page + 1,
hasMore: res.hasMore
})
}
})
5. 小程序性能分析工具
1. 微信开发者工具 Audits 面板
- 体验评分:启动性能、页面渲染、用户体验
- 结合常见优化建se建议
2. Timeline / Performance 面板
- 录制页面操作过程
- 分析帧率、Script 耗时、Rendering 耗时
3. 真机调试
- 阴影模式测试(模拟低配置设备)
- 强制缓慢网络测试
6. 资深面试题
- 小程序首屏渲染优化思路?
- 减少主包体积,提卸加载到分包
- 首屏数据在 onLoad 中请求并打开下一页齐平行发起
- 马鼓属屏 + 骨架屏 + 首屏内容尽早请求
- 首屏外的图片延迟加载
- setData 渲染 1000 条数据如何优化?
- 虚拟列表组件(只渲染可视区域)
- 分屏渲染,每帧只 setData 一小批
- 尸体数据尽量小,只存 key 字段而非全量对象
- 小程序页面切换数据如何保存?
- 小程序没有路由層缓存,页面切换后重新创建
- 解决:app.globalData / Storage / 页面层参数传递
- Taro/uni-app 可配置页面缓存 (keepAlive)