做小程序最怕用户反馈“一卡一卡的”。很多时候不是逻辑复杂,而是我们踩了渲染层的坑。这篇文章结合我最近一个电商小程序从平均帧率 43fps 提升到 58fps 的实战,聊聊卡顿的根因和可落地的优化手段。
一、先定位:卡顿到底卡在哪
小程序是双线程架构:逻辑层(JS)和渲染层(WebView)通过 setData 通信。卡顿通常来自三处:
- 逻辑层 JS 执行时间过长,阻塞了事件响应;
setData数据量过大或调用过于频繁,跨线程通信和 diff 开销高;- 渲染层节点过多、样式复杂,导致布局与重绘压力大。
定位工具首选开发者工具的 Performance 面板,重点看 FPS 曲线和 setData 调用记录;再用 Trace 看长任务。真机上可以打开“性能监控”看内存与 CPU 曲线。
二、setData 是重灾区,必须精打细算
1. 只传变化的数据,用路径写法
很多人图省事整对象回写,结果把没变的数据也传了一遍。正确做法是用数据路径:
// 差:整个 list 回写
this.setData({ list: newList });
// 好:只改第 3 项的 price
this.setData({
'list[2].price': 19.9
});路径写法让 diff 只针对单个字段,通信体积能降一个数量级。
2. 合并调用,做节流
滚动、输入这类高频事件里如果每次都 setData,渲染层根本来不及处理。建议合并成一次,或者用节流:
let timer = null;
function onScroll(e) {
if (timer) return;
timer = setTimeout(() => {
this.setData({ scrollTop: e.detail.scrollTop });
timer = null;
}, 100);
}另外,能放在 onPageScroll 里做的计算,尽量少触发 setData,比如只存到实例属性上,等需要时再更新视图。
3. 避免在 setData 里传大对象和函数
不要把整个后端返回的原始数据丢进 data。先做裁剪,只保留视图需要的字段;函数、Date、undefined 这类值无法被序列化,也会拖慢通信。
三、长列表:虚拟列表 + 分页
一个页面渲染 500 个商品卡片,节点数轻松破万,滚动必卡。解决方案是虚拟列表:只渲染可视区域内的节点,用占位撑起滚动高度。核心思路:
- 容器固定高度,内部用一个高度为
total * itemHeight的占位元素; - 监听滚动,计算起始索引,只渲染
[start, start + visibleCount]; - 用
transform: translateY做偏移,避免重排。
如果列表项高度不固定,可以用 wx:for 配合 wx:key 保证复用,再配合分页加载,首屏只渲染 20 条。
四、图片与节点优化
- 图片必须指定
width/height,避免布局抖动;优先用 webp,并按需加lazy-load。 - 减少深层嵌套的
view,能用flex解决的就别套多层; - 少用
box-shadow、filter、大范围border-radius这类触发重绘的样式,尤其在滚动容器内。 - 动画优先用 CSS 的
transform和opacity,它们走合成层,不触发重排。
五、逻辑层:别让主线程闲着也别让它忙死
复杂计算(比如排序、格式化大量数据)不要放在 setData 前同步执行。可以拆成小块用 setTimeout 分片,或者放进 Worker。另外,页面 onHide 时记得清理定时器、取消未完成的请求,避免后台空转。
六、一个真实优化清单
- 把列表页的整对象
setData改成路径更新,通信耗时从 18ms 降到 4ms; - 滚动事件加 100ms 节流,FPS 从 43 提升到 52;
- 长列表改虚拟列表,首屏节点从 8000+ 降到 600 左右,稳定 58fps;
- 图片统一加尺寸和懒加载,内存峰值下降约 30%。
卡顿优化没有银弹,核心就是减少跨线程通信、减少渲染节点、减少重绘重排。先用工具找到瓶颈,再针对性下手,别凭感觉改代码。
评论 (0)
还没有评论,快来抢沙发吧~