为什么要重视小程序性能?
很多开发者觉得小程序嘛,功能简单,性能差一点也没关系。但实际上,小程序的性能直接影响用户体验和业务数据:
- 启动慢:用户点进来,等半天白屏,直接就走了,转化率掉一大截;
- 列表卡:滑动的时候一卡一卡的,用户体验很差,不想往下翻;
- 包体积大:超过2M的主包,用户都下载不了,连入口都没有;
- 内存高:用着用着就闪退了,尤其是低端机上更明显。
我们做过统计,一个小程序的启动时间从3秒优化到1秒,转化率能提升20%以上。性能不是”锦上添花”,是”基本功”。
这几年我们做了几十个小程序项目,积累了不少性能优化的经验。今天就从几个维度,系统地聊聊小程序性能优化到底该怎么做。
一、启动性能优化:让用户快点看到内容
启动性能是用户对小程序的第一印象,也是最重要的优化点。
1. 分包加载:最有效的优化手段
分包加载是小程序性能优化的第一利器,没有之一。
主包只放首页和一些公共的东西,其他页面都放到分包里。用户打开小程序的时候,只下载主包,进入分包页面的时候再下载分包。
优化效果非常明显:
- 主包体积从2M降到500K,启动时间能减少一半以上;
- 包体积上限也提高了——整个小程序可以到20M(主包2M + 多个分包)。
分包的原则:
- 主包尽量小:只放TabBar页面和公共资源;
- 按功能分包:一个功能模块一个分包;
- 按需加载:不常用的功能都扔到分包里。
2. 分包预下载:让用户感觉不到加载
光有分包还不够,如果用户点进分包页面的时候才开始下载,还是要等。
这时候就要用分包预下载:在进入某个页面的时候,提前把可能会用到的分包下载好。等用户真正点进去的时候,分包已经下好了,直接就能打开。
比如首页有个”我的订单”按钮,用户点进去要跳转到订单分包。那我们可以配置:进入首页的时候,预下载订单分包。这样用户点的时候,分包已经下好了,秒开。
预下载的时机要选好——不要在首页刚加载的时候就预下载一堆分包,那样反而会拖慢首页的加载。选在首页渲染完、空闲的时候再预下载。
3. 首屏渲染优化:先看到内容再说
首屏内容要尽快出来。不要等所有数据都加载完了才渲染,先把骨架屏或者静态内容渲染出来,数据回来了再填充。
几个技巧:
- 骨架屏:数据加载的时候,先显示骨架屏占位,用户感觉没那么慢;
- 首屏数据优先:首屏需要的接口优先请求,其他的往后排;
- 缓存策略:上次的数据先展示出来,新数据回来了再更新。用户先看到旧数据,也比白屏强。
4. 代码注入优化
小程序启动的时候,要把所有JS代码都注入到WebView里。代码越多,注入越慢。
怎么减少注入的代码量?
- 按需引入:不要整个库都引进来,只引入你用到的部分。比如lodash,不要整个import,要用哪个函数就引哪个;
- Tree Shaking:确保构建工具开启了Tree Shaking,把没用到的代码摇掉;
- 公共代码抽离:多个分包都用到的代码,抽到主包里或者独立分包里,不要每个分包都打一份。
二、渲染性能优化:滑动要流畅
启动完了,用起来卡不卡,就看渲染性能了。
1. 长列表优化:最常见的性能问题
长列表是小程序里最容易卡的地方。几百条数据,全部渲染出来,DOM节点太多,滑动的时候肯定卡。
优化方案:
方案一:分页加载
不要一次性把所有数据都加载出来,一页一页加载。滑到底部了再加载下一页。
这个基本操作,大家应该都知道。
方案二:虚拟列表
如果数据量特别大(上千条),分页加载也不够——就算分页,滚到后面DOM节点还是越来越多,还是会卡。
这时候就要用虚拟列表:只渲染可视区域的节点,滚出去的节点销毁掉,新滚进来的再创建。这样不管有多少条数据,DOM节点数量始终是固定的,怎么滑都不卡。
小程序官方有 recycle-view 组件,可以直接用。也有一些第三方的虚拟列表组件。
方案三:列表项优化
就算用了分页和虚拟列表,列表项本身太重的话,也会卡。
优化列表项:
- 减少节点数量:列表项里的DOM节点越少越好;
- 避免复杂布局:不要在列表项里搞太复杂的布局,尤其是嵌套很深的flex;
- 图片懒加载:列表里的图片,用懒加载,进入可视区域再加载。
2. setData 优化:最容易被忽略的优化点
小程序的 setData 是性能重灾区。很多人写代码的时候,不管什么数据都往 setData 里塞,而且每次都塞一大堆。
但你知道吗?setData 是有成本的。每次 setData,都要:
- 逻辑层把数据传到渲染层(跨线程通信,有开销);
- 渲染层重新计算、重新渲染。
如果 setData 太频繁、数据量太大,肯定会卡。
优化原则:
- 只传需要更新的数据:不要每次都把整个对象传进去,只传变了的那部分。支持路径写法,比如
this.setData({ 'user.name': 'xxx' }); - 合并多次 setData:短时间内多次
setData,合并成一次。比如在一个循环里不要每次都setData,循环完了再统一setData一次; - 不要 setData 页面不可见的数据:用户看不到的数据,不要往
setData里塞。比如一些计算用的中间变量,存在this上就行了,不用放到 data 里; - 大数据量不要直接 setData:比如长列表,不要一次性把几百条数据都
setData进去,分批来。
3. 减少重渲染
页面里的数据一变,整个页面都要重新渲染吗?不一定。
可以用一些方法减少不必要的重渲染:
- 组件化:把页面拆成组件,数据变了只更新对应的组件,不用整个页面都重绘;
- 纯数据字段:有些数据只是用来计算的,不需要渲染。用
pureDataPattern把它们标记为纯数据字段,这些数据变了不会触发重渲染; - 合理使用 observer:不要什么数据变化都触发一堆计算,能算一次的不要算多次。
三、包体积优化:越小越好
包体积直接影响启动速度,而且还有2M主包的硬限制。包体积优化是必须做的。
1. 图片优化:占比最大的部分
一般来说,小程序包里占比最大的就是图片。图片优化好了,包体积能降一大截。
优化方法:
- 图片放CDN:不要把图片都放到小程序包里,能放CDN的都放CDN。小程序包只放一些必须的小图标;
- 用WebP格式:同样的图片,WebP比JPG/PNG小30%以上。小程序支持WebP,放心用;
- 压缩图片:图片上线前一定要压缩。用tinypng之类的工具,压完肉眼看不出区别,但体积小很多;
- 雪碧图:小图标合并成雪碧图,减少请求数量,也减少文件数量。
2. 代码优化
图片之外,就是代码了。
- 去掉不用的代码:定期清理,把不用的页面、组件、工具函数都删掉。很多项目里有大量的僵尸代码,占地方又没用;
- 精简第三方库:引进第三方库的时候要慎重。一个库动不动几十K,值不值得?能不能自己写个简单的替代?
- 按需引入:前面说过了,不要整个库都引进来,只引你用到的部分;
- 模板优化:WXML里不要有太多无用的节点,能合并的合并。
3. 分包优化
前面说过分包加载,分包也是控制包体积的重要手段。
- 主包只留必须的:主包尽量小,能扔到分包里的都扔进去;
- 独立分包:有些功能跟主包关系不大,可以做成独立分包。独立分包不依赖主包,可以单独打开,启动更快;
- 分包异步化:有些组件或者JS模块,不是首屏必须的,可以异步加载,用的时候再加载。
四、网络优化:快、稳、省
网络请求也是影响体验的重要因素。接口慢,页面就慢。
1. 减少请求数量
请求越少越好。
- 合并接口:首屏需要的几个接口,如果能合并成一个,就合并成一个。减少一次HTTP请求,省了很多时间;
- 雪碧图:前面说过了,小图标合并,减少图片请求;
- 缓存:能缓存的数据就缓存,不用每次都请求。
2. 加快请求速度
- CDN加速:静态资源都放CDN,就近访问,速度快;
- HTTP/2:服务器支持HTTP/2的话,多路复用,更快;
- 接口性能优化:后端接口本身要快。慢接口要优化,该加索引加索引,该加缓存加缓存。
3. 预请求
跟分包预下载一个道理——提前请求。
比如用户在列表页,点进去要看详情页。我们可以在用户hover列表项的时候(或者列表页加载完的时候),就提前请求详情页的数据。等用户真正点进去的时候,数据已经回来了,直接展示。
当然,预请求也要控制好度,不要预请求太多,浪费流量。
4. 弱网优化
网络不好的时候怎么办?不能一直白屏等着。
- ** loading 状态**:请求的时候要有loading,让用户知道在加载;
- 失败重试:网络不好请求失败了,自动重试几次,或者给用户一个重试按钮;
- 离线缓存:一些不经常变的数据,缓存到本地。没网的时候也能看,有网了再更新。
五、内存优化:不要闪退
内存太高,小程序就会闪退。尤其是在低端机上,内存本来就小,更容易崩。
1. 图片内存
图片是内存大户。图片太多、太大,内存蹭蹭往上涨。
优化:
- 图片不要太大:列表里的缩略图,用缩略图的URL,不要用原图;
- 及时回收:离开页面了,图片就不要再占内存了。长列表的话,滚出去的图片可以回收;
- 避免同时加载大量图片:一屏之内不要有太多图片,用懒加载。
2. 页面栈
小程序的页面栈最多10层。如果用户一直往里面跳,页面越来越多,内存也越来越高。
优化:
- 合理使用 redirect:不需要返回的页面,用 redirectTo 而不是 navigateTo,这样旧页面就销毁了;
- 返回多层:比如从A→B→C→D,D操作完要回到A,不要一步步返回,用 navigateBack 直接回到A,中间的页面都销毁了。
3. 定时器和事件监听
很多人容易忘——页面卸载的时候,定时器和事件监听要清掉。
setInterval、setTimeout:页面onUnload的时候要清掉;wx.onXXX之类的事件监听:页面卸载的时候要off掉;- 不然的话,页面虽然看不见了,但还在内存里跑,内存泄漏。
4. 及时释放数据
页面里存了很多大数据(比如长列表数据),页面销毁的时候要清掉。不然页面对象一直引用着这些数据,GC回收不了。
onUnload 的时候,把大的数据都置为 null,帮助GC回收。
六、怎么发现性能问题?
优化之前,得先知道问题在哪。不能瞎优化。
1. 小程序开发者工具
微信开发者工具有性能面板,可以看启动时间、渲染时间、内存使用、网络请求等等。
- 性能Trace:可以录制一段时间的性能数据,然后分析哪里慢;
- 代码依赖分析:可以看到每个包的大小、每个文件的大小,找出占空间大的文件;
- 内存面板:看内存使用情况,有没有内存泄漏。
2. 真机调试
开发者工具里的性能跟真机还是有差距的。一定要在真机上测,尤其是低端机。
- 真机调试:连接真机,看性能数据;
- 体验评分:小程序后台有体验评分,会自动检测性能问题,给出优化建议。这个很有用,建议定期跑一下。
3. 性能监控
上线之后,也要监控性能。不能只在开发的时候优化,上线了就不管了。
- 自定义打点:在关键的地方打点,比如启动时间、首屏时间、页面加载时间,上报到后台;
- 监控平台:用小程序的性能监控,或者接入第三方APM工具,实时监控线上性能。
写在最后
性能优化这件事,不是一蹴而就的,也不是一劳永逸的。
不是说优化完一次就完事了——随着需求迭代,代码越来越多,性能又会慢慢变差。所以性能优化是一个持续的过程,要常抓不懈。
而且性能优化也不是”为了优化而优化”。所有的优化,最终都是为了更好的用户体验、更好的业务数据。优化之前,先搞清楚:用户最痛的点是什么?哪个地方对业务影响最大?先优化那些地方,投入产出比最高。
希望这篇文章能帮到你。如果你们的小程序也有性能问题,不妨照着这些方法试一试。
Why Care About Mini Program Performance?
Many developers think mini programs are simple features, so a bit of poor performance doesn’t matter. But in reality, mini program performance directly affects user experience and business metrics:
- Slow startup: Users tap in, wait forever with a white screen, and leave directly — conversion rate drops significantly.
- Laggy lists: Stuttering while scrolling, poor user experience, people don’t want to scroll down.
- Large package size: Main package exceeding 2MB — users can’t even download it, no entry point.
- High memory: Crashes after using for a while, especially noticeable on low-end phones.
We’ve calculated that reducing a mini program’s startup time from 3 seconds to 1 second can increase conversion rate by over 20%. Performance isn’t “icing on the cake” — it’s a basic requirement.
Over the years, we’ve built dozens of mini program projects and accumulated a lot of performance optimization experience. Today let’s systematically talk about how to optimize mini program performance across several dimensions.
1. Startup Performance: Let Users See Content Fast
Startup performance is users’ first impression of your mini program, and the most important optimization point.
1. Subpackage Loading: The Most Effective Optimization
Subpackage loading is the #1 tool for mini program performance optimization, bar none.
The main package only contains the home page and common resources. Other pages go into subpackages. When users open the mini program, they only download the main package. When they navigate to a subpackage page, the subpackage downloads then.
The optimization effect is dramatic:
- Reducing main package size from 2MB to 500KB can cut startup time by more than half.
- Package size limit also increases — total mini program can be up to 20MB (2MB main + multiple subpackages).
Subpackage principles:
- Keep main package small: Only TabBar pages and shared resources.
- Subpackage by feature: One feature module per subpackage.
- Load on demand: Uncommon features all go into subpackages.
2. Subpackage Pre-Download: Make Users Feel No Loading
Just having subpackages isn’t enough. If users only start downloading when they tap into a subpackage page, they still have to wait.
This is where subpackage pre-download comes in: when entering a page, pre-download subpackages that users are likely to need. By the time users actually tap in, the subpackage is already downloaded — opens instantly.
For example, the home page has a “My Orders” button that navigates to the orders subpackage. We can configure: when entering the home page, pre-download the orders subpackage. Then when users tap it, it’s already downloaded — instant open.
Choose pre-download timing carefully — don’t pre-download a bunch of subpackages right when the home page loads, that would actually slow down home page loading. Do it after the home page finishes rendering, during idle time.
3. First Screen Rendering Optimization: See Content First
First screen content should appear as soon as possible. Don’t wait for all data to load before rendering — render skeleton screen or static content first, then fill in data when it comes back.
A few techniques:
- Skeleton screen: While data loads, show a skeleton screen as placeholder — users feel it’s less slow.
- First screen data priority: Request APIs needed for first screen first, others come later.
- Cache strategy: Show last time’s data first, update when new data comes back. Users seeing old data is better than a white screen.
4. Code Injection Optimization
When a mini program starts, all JS code has to be injected into the WebView. More code = slower injection.
How to reduce injected code:
- Import on demand: Don’t import entire libraries — only import what you use. For example, with lodash, don’t import the whole thing — import just the functions you need.
- Tree Shaking: Make sure your build tool has Tree Shaking enabled — shake out unused code.
- Extract common code: Code used by multiple subpackages — extract it into the main package or an independent subpackage. Don’t bundle a copy in each subpackage.
2. Rendering Performance: Smooth Scrolling
After startup, whether it feels smooth to use depends on rendering performance.
1. Long List Optimization: The Most Common Performance Issue
Long lists are the most common cause of lag in mini programs. Hundreds of items, all rendered at once — too many DOM nodes, definitely laggy when scrolling.
Optimization solutions:
Solution 1: Pagination
Don’t load all data at once — load page by page. Load next page when scrolling to the bottom.
This is basic, everyone should know this.
Solution 2: Virtual List
If data volume is especially large (thousands of items), pagination isn’t enough — even with pagination, DOM nodes keep accumulating as you scroll, still gets laggy.
This is when you need virtual lists: only render nodes in the visible area. Nodes that scroll out get destroyed, new ones scrolling in get created. This way, no matter how many items there are, the number of DOM nodes stays constant — never lags.
WeChat has an official recycle-view component you can use directly. There are also third-party virtual list components.
Solution 3: List Item Optimization
Even with pagination and virtual lists, if list items themselves are too heavy, it’ll still lag.
Optimize list items:
- Reduce node count: fewer DOM nodes inside list items is better.
- Avoid complex layouts: Don’t put overly complex layouts in list items, especially deeply nested flex.
- Image lazy loading: Images in lists use lazy loading — load when they enter the visible area.
2. setData Optimization: The Most Overlooked Optimization Point
setData is a major performance hotspot in mini programs. Many people just stuff everything into setData, and stuff huge amounts every time.
But do you know? setData has costs. Every setData requires:
- Logic layer sending data to render layer (cross-thread communication, has overhead).
- Render layer recalculating and re-rendering.
If setData is too frequent or data volume is too large, it will definitely lag.
Optimization principles:
- Only pass data that needs updating: Don’t pass the whole object every time — only pass the part that changed. Supports path syntax, like
this.setData({ 'user.name': 'xxx' }). - Merge multiple setData calls: Multiple
setDatacalls in a short time — merge into one. For example, don’tsetDatainside a loop every iteration — do it once after the loop finishes. - Don’t setData for invisible data: Data users don’t see — don’t put it in
setData. Intermediate variables for calculations just store onthis, no need to put in data. - Don’t setData large datasets at once: For long lists, don’t
setDatahundreds of items all at once — do it in batches.
3. Reduce Re-renders
When data changes in a page, does the whole page have to re-render? Not necessarily.
You can use methods to reduce unnecessary re-renders:
- Componentization: Split pages into components. When data changes, only update the corresponding component — don’t redraw the whole page.
- Pure data fields: Some data is only used for calculation, doesn’t need rendering. Use
pureDataPatternto mark them as pure data fields — changes to these won’t trigger re-renders. - Use observers wisely: Don’t trigger a bunch of calculations for every data change. What can be calculated once, don’t calculate multiple times.
3. Package Size Optimization: Smaller is Better
Package size directly affects startup speed, and there’s the hard 2MB main package limit. Package size optimization is a must.
1. Image Optimization: The Biggest Chunk
Generally speaking, images take up the largest proportion of mini program package size. Optimize images well and you can reduce package size significantly.
Optimization methods:
- Put images on CDN: Don’t put all images in the mini program package. Put whatever you can on CDN. Only keep essential small icons in the package.
- Use WebP format: Same image, WebP is 30%+ smaller than JPG/PNG. Mini programs support WebP, use it confidently.
- Compress images: Always compress images before going live. Use tools like tinypng — after compression you can’t tell the difference visually, but size is much smaller.
- Sprite sheets: Combine small icons into sprite sheets — reduces request count and file count.
2. Code Optimization
Besides images, there’s code.
- Remove unused code: Regular cleanup — delete unused pages, components, utility functions. Many projects have tons of dead code taking up space for no reason.
- Trim third-party libraries: Be careful when introducing third-party libraries. A library can be dozens of KB — is it worth it? Can you write a simple alternative yourself?
- Import on demand: As mentioned before — don’t import whole libraries, only import what you use.
- Template optimization: Don’t have too many useless nodes in WXML. Merge what you can.
3. Subpackage Optimization
We talked about subpackage loading earlier — subpackages are also an important way to control package size.
- Main package only has essentials: Keep main package as small as possible. Whatever can go into subpackages, put it there.
- Independent subpackages: Some features not closely related to the main package can be made into independent subpackages. Independent subpackages don’t depend on the main package, can be opened separately — faster startup.
- Subpackage asynchronization: Some components or JS modules not needed for first screen can be loaded asynchronously — load when needed.
4. Network Optimization: Fast, Stable, Efficient
Network requests are also an important factor in experience. Slow APIs = slow pages.
1. Reduce Request Count
Fewer requests = better.
- Merge APIs: If first screen needs several APIs, merge them into one if possible. One less HTTP request saves a lot of time.
- Sprite sheets: As mentioned before — combine small icons, reduce image requests.
- Caching: Data that can be cached — cache it, don’t request every time.
2. Speed Up Requests
- CDN acceleration: Static resources all on CDN — nearest access, faster.
- HTTP/2: If server supports HTTP/2, multiplexing — faster.
- API performance optimization: Backend APIs themselves need to be fast. Slow APIs need optimization — add indexes where needed, add caching where needed.
3. Pre-Requests
Same idea as subpackage pre-download — request ahead of time.
For example, user is on a list page, taps in to see detail page. We can pre-request detail page data when users hover over list items (or when the list page finishes loading). By the time users actually tap in, data is already back — display immediately.
Of course, control the degree of pre-requesting. Don’t pre-request too much and waste bandwidth.
4. Weak Network Optimization
What about when the network is bad? Can’t just wait with a white screen forever.
- Loading state: Show loading during requests — let users know it’s loading.
- Retry on failure: If request fails due to bad network, auto-retry a few times, or give users a retry button.
- Offline cache: Some data that doesn’t change often — cache locally. Viewable without network, update when network is available.
5. Memory Optimization: Don’t Crash
High memory and the mini program crashes. Especially on low-end phones with less memory — easier to crash.
1. Image Memory
Images are memory hogs. Too many images, too large — memory skyrockets.
Optimization:
- Don’t use oversized images: Thumbnails in lists — use thumbnail URLs, not original full-size images.
- Recycle promptly: When leaving a page, images shouldn’t keep taking up memory. For long lists, images that scroll out can be recycled.
- Avoid loading too many images simultaneously: Don’t have too many images on one screen — use lazy loading.
2. Page Stack
Mini program page stack maxes out at 10 levels. If users keep navigating deeper, more and more pages — higher and higher memory.
Optimization:
- Use redirect appropriately: Pages you don’t need to return from — use redirectTo instead of navigateTo. Then the old page is destroyed.
- Go back multiple levels: For example, A→B→C→D, after D is done you need to go back to A — don’t go back step by step. Use navigateBack to go directly back to A. Pages in between are all destroyed.
3. Timers and Event Listeners
Many people forget — when a page unloads, timers and event listeners need to be cleaned up.
setInterval,setTimeout: Clear them on page onUnload.wx.onXXXevent listeners: Off them when page unloads.- Otherwise, even though the page isn’t visible, it’s still running in memory — memory leak.
4. Release Data Promptly
Pages storing lots of big data (like long list data) — when page unloads, clear it. Otherwise the page object keeps referencing the data, GC can’t collect it.
During onUnload, set big data to null to help GC回收 (collect).
6. How to Find Performance Issues?
Before optimizing, you need to know where the problems are. Can’t optimize blindly.
1. Mini Program DevTools
WeChat DevTools has a performance panel — can see startup time, render time, memory usage, network requests, etc.
- Performance Trace: Record performance data for a period of time, then analyze what’s slow.
- Code Dependency Analysis: See size of each package, each file — find large files.
- Memory Panel: See memory usage, check for memory leaks.
2. Real Device Debugging
Performance in DevTools differs from real devices. Always test on real devices, especially low-end phones.
- Real device debugging: Connect to real device, see performance data.
- Experience score: Mini program backend has experience scoring — automatically detects performance issues, gives optimization suggestions. Very useful, recommend running it regularly.
3. Performance Monitoring
After going live, you also need to monitor performance. Can’t just optimize during development and forget about it after launch.
- Custom tracking: Add tracking at key points — like startup time, first screen time, page load time — report to backend.
- Monitoring platform: Use mini program’s built-in performance monitoring, or integrate third-party APM tools — monitor live performance in real-time.
Closing Thoughts
Performance optimization isn’t something you do once and you’re done. It’s not a one-time thing and it’s not permanent.
It’s not like you optimize once and that’s it — as requirements iterate, code grows, performance gradually degrades again. So performance optimization is an ongoing process — you need to keep at it.
And performance optimization isn’t “optimizing for optimization’s sake.” All optimization ultimately serves better user experience and better business metrics. Before optimizing, figure out first: what’s the most painful point for users? Where has the biggest business impact? Optimize those first — highest ROI.
Hope this article helps you. If your mini program also has performance issues, why not try these methods?