以下将从三个方面对小程序的内存优化进行分析:内存膨胀,内存泄漏和内存频繁GC
内存膨胀
内存膨胀优化的基本原则: 尽量少的资源,尽量小的资源
-
合理使用分包加载
- 使用分包加载不仅能优化启动耗时,也能够实现页面、组件和逻辑较粗粒度的按需加载,从而降低内存的占用。
-
使用按需注入和用时注入
-
- 页面维度,仅注入当前访问的页面的代码,其它未访问的暂不注入执行
- 通常情况下,在小程序启动时,启动页面依赖的所有代码包(主包、分包、插件包、扩展库等)的所有 JS 代码会全部合并注入 ,包括其他未访问的页面以及未用到自定义组件,同时所有页面和自定义组件的 JS 代码会被立刻执行。这造成很多没有使用的代码在小程序运行环境中注入执行,影响注入耗时和内存占用。
- 开启按需注入,即在app.json中加入如下代码。
-
{"lazyCodeLoading": "requiredComponents"}
-
- 组件维度,为自定义组件添加占位组件,在启动时不注入,真正渲染时再进行注入
- 前提条件:需先开启「按需注入」
- 配置方法: 在页面或自定义组件对应的 JSON 配置中的
componentPlaceholder
字段用于指定占位组件,如下 -
{ "usingComponents": { "comp-a": "../comp/compA", "comp-b": "../comp/compB", "comp-c": "../comp/compC"}, "componentPlaceholder": { "comp-a": "view", "comp-b": "comp-c" } }
-
-
控制 WXML 节点数量和层级
-
一个太大的 WXML 节点树会增加内存的使用,样式重排时间也会更长,影响体验。
-
建议一个页面 WXML 节点数量应少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个
-
优化措施
-
使用虚拟列表,有效的控制wxml节点的数量,避免内存的持续膨胀,下图我实现了一版(利用分页的特点进行分屏和计算高度,然后根据页面scrollTop判断展示哪几屏,恒定渲染两到三页的数据),目前还有一些细节有待优化
-
-
-
控制图片资源的大小
- 开发者应根据功能需要和实际显示区域的大小,选择合适的图片尺寸、图片格式和压缩比。
- 目前小程序内有对图片进行cdn裁剪,压缩和格式转化,在保证清晰度的同时实现了尽量小的文件大小
内存泄漏
以下是小程序常见的内存泄露问题
5.1 小程序长期持有页面实例,导致页面实例和引用的组件无法正常销毁
页面unload之后,基础库会从页面栈中将页面实例清理。正常情况下,js垃圾回收机制会将页面进行回收,释放内存。
但如果开发者代码中持有的页面实例(this)未释放,则会导致页面未被正常回收,引起内存泄漏。建议开发者注意,并在unload中进行必要的清理
案例一: 页面实例被页面外变量或全局变量引用
函数闭包内持有页面的this,且函数被挂到了全局或页面生命周期外的变量,会导致页面无法释放
案例二: 页面实例被异步回调长时间引用
如果在长时间未返回的异步回调中访问了页面的this,如持续时间过长的setTimeout、setInterval,耗时较长的wx API回调(如长时间的wx.request等),会导致页面无法释放
案例三: 页面实例被未解绑的事件监听引用
事件监听器中持有了页面的this,如果页面销毁后监听未被解绑,会导致页面无法被释放
5.2 事件监听结束未及时解绑
事件监听结束后,应及时解绑监听器
小程序内类似问题:
- 部分页面intersectionObserve.observe 没有在页面Unload的时候desconnect
- Events.on,Events.trigger方法没有及时移除?
5.3 未清理的定时器
开发者在开发如【秒杀倒计时】等功能时,可能会使用setInterval设置定时器,页面或组件销毁前,需要调用clearInterval方法取消定时器
目前小程序的机制,如果组件内存在setInterval或者setTimeout,想要在页面离开的时候销毁
需要注意:页面返回需要在页面监听 onUnload然后调用组件内的方法移除,而页面navigate的时候需要在页面的onHide里监听并移除
内存频繁 GC
GC执行的特别频繁,一般出现在频繁使用大的临时变量导致新生代空间被装满的速度极快,而每次新生代装满时就会触发 GC,频繁 GC 同样会导致页面卡顿,想要避免的话就不要搞太多的临时变量,因为临时变量不用了就会被回收。