Skip to main content

小程序性能优化手段

截至 2026 年 10 月,小程序是双线程架构,逻辑层与渲染层通信要过桥——所有性能问题几乎都和「过桥的数据量」有关。优化方向不是「少渲染」,而是「少传数据、传得准」。

一、双线程与通信桥​

小程序的逻辑层与渲染层是两个独立线程,视图更新要靠数据跨线程传递并序列化。这个架构决定了所有性能问题的根源:每次 setData 都是一次跨线程通信 + 一次视图层重建。逻辑层改了数据,要 JSON 序列化、过 Native 中转、视图层再反序列化并 diff,整套链路才完成一次渲染。

所以优化的第一原则不是「少渲染」,而是「少传数据、传得准」——把数据量压下去,比纠结渲染细节管用得多。

一个反直觉的点:逻辑层算得再快也没用,因为结果必须过桥才能显示。所以「在逻辑层多做计算减少 setData 次数」和「减少每次 setData 的数据量」是两个独立方向,前者减少通信频次,后者减少单次通信成本,都要抓。

所有性能问题都源于这个双线程架构,优化要从通信量入手

逻辑层(AppService)JS 运行在此,没有 DOM / window负责数据计算与 setData渲染层(WebView)负责真实渲染数据变化后重建视图setData:序列化后过桥事件回传卡顿的根因多数不在渲染,而在过桥的数据量:data 里的每个字段都会参与序列化与传输 —— 用不到的字段不要放进 data优化方向:只传变化的最小字段、扁平化数据结构、列表用官方复用节点、后台页面停掉轮询与动画。
图:双线程之间通过桥通信,性能瓶颈通常出现在通信量而不是渲染

二、一次传多少数据​

三条最有效。

第一,只传变化的字段,用路径写法而不是把整个对象重传。一次 setData({ list }) 会把整个 list 序列化过去,而 list[3].name 只传一个字段。

// 路径更新:只传一个字段,而不是重传整个 list
this.setData({ ['list[' + index + '].checked']: true })

第二,合并更新,把一帧内的多次调用合起来,避免一次交互触发多次通信。第三,控制数据量:data 里的任何字段都会被序列化参与传输,不需要渲染的大字段(比如原始配置、临时缓存)不要放进 data,挂在 this 上即可。这条最容易被忽略——很多人把所有状态都堆在 data 里,结果每次 setData 都背着一堆用不上的大对象一起过桥。

// ❌ 多次 setData,触发多次跨线程通信
this.setData({ loading: true })
this.setData({ list: [] })
this.setData({ total: 0 })
// ✅ 合并成一次
this.setData({ loading: true, list: [], total: 0 })

多数列表页滚动卡顿,根因不是渲染慢,而是 setData 数据量太大。

几个边界要记住:单次 setData 的数据经 JSON.stringify 后不得超过 1024KB;调用频率官方建议不超过 20 次/秒,实测超过 25 次/秒会触发队列节流;同时发起的网络请求上限是 10 个。分包方面,主包与单个分包都不超过 2MB,整包不超过 20MB。

三、减少不必要的重渲染​

节点层级与数量直接影响 diff 成本,结构越浅越好,不要套无意义的容器层。长列表要用官方提供的复用机制(虚拟列表 / 复用池),而不是自己算可视区——框架的复用能力比手写稳,还能跨页面复用节点、减少创建销毁。图片走 CDN 压缩与合适尺寸,懒加载,避免大图一次性进场。复杂选择器(如后代选择器层层匹配)也会增加样式计算,尽量用类选择器直达。

结构越浅越好,长列表交给官方复用能力

四、分包与首屏​

启动慢往往不是「代码多」,而是「启动时做了不该做的事」。分包加载让首屏只下主包,其余按需下载——这同时改善了首次启动,因为分包是按需拉取的。按需注入减少不必要的启动逻辑,初始渲染缓存让首屏更快出内容。还要减少 App 里的同步逻辑与大体量依赖:启动时同步读大文件、同步调重接口,都会拖慢首屏。

// 分包配置:把非首屏模块拆到 subPackages,按需下载
{
"pages": ["pages/index/index"],
"subPackages": [
{ "root": "packageA", "pages": ["detail/detail"] }
]
}

分包不只是解决包体积,它同时改善首次启动

列表里的图片是内存大户,懒加载和尺寸都要管:

<image
lazy-load
src="{{item.cover}}"
mode="aspectFill"
webp
/>

五、图片与内存​

预请求与数据预拉取能把耗时藏到空闲时段,骨架屏避免首屏白屏。骨架屏的本质是占位而不是真数据——它只是让用户感知到页面在加载,避免白屏带来的流失感。后台页面要清理定时器与动画——逻辑层是单线程,后台页面的 setData 会和前台抢资源,而用户根本看不到后台的渲染。在 onHide 里停掉轮询与动画,onShow 再恢复。

后台页面别空转,该停的轮询和动画要停

分包下载完再跳转仍有等待,预下载能把它提前掉:

{
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["subpkg-order"]
}
}
}

六、少渲染解决不了全部​

setData 卡顿多数不是渲染慢,而是数据量太大导致序列化与传输慢——瓶颈在通信桥,不在渲染层。

把数据放进 data 也不是没代价:data 里的任何字段都会参与序列化与传输,用不上的字段别放。

分包同样不只是为了解决包体积——分包是按需下载的,它同时改善首次启动。

后台页面也别空转,该停的轮询和动画要停下来。

官方的 性能优化指南 给出了启动与渲染的具体建议,setData 那部分是重点。