在开发微信小程序时,若想实现性能优化,有两个核心要点不可忽视,即提升加载性能与提升渲染性能。下面我们先聚焦于如何提高加载性能。
当用户轻点小程序图标后,背后究竟发生了哪些事情呢?
小程序的启动之旅包含了资源准备(也就是代码包下载)、业务代码注入与渲染以及异步请求数据这几个关键环节。这三个环节在日常开发中十分常见,并且它们分别对应着小程序的不同状态。
你是否留意过,当打开小程序时,有时会出现带有三个点的白屏界面,其实这个阶段就是在下载代码包;而没有三个点的白屏界面,则意味着小程序正在进行业务代码的注入和渲染;当界面显示 “加载中” 字样时,表明业务代码正在进行异步请求数据。
概括来说,小程序呈现在用户眼前,实则经历了两大阶段。其一为运行环境的加载,其二是下载代码包来启动小程序。
值得一提的是,运行环境的预加载工作是由微信完成的。微信会具备前瞻性地在用户打开小程序之前就把运行环境准备妥当。如此一来,当用户点击小程序入口时,就能够直接进入下载小程序代码包的流程。
这里需要明确的是,小程序代码包中的代码并非原始的小程序源代码,而是经过编译、压缩以及打包等一系列处理后的代码包。
我们可以借助一个形象的图示来理解,图的左侧 “预加载” 代表的就是运行环境的预加载,而右侧的 “小程序启动” 则对应着下载代码包来启动小程序的过程。
在微信小程序的世界里,其提供的运行环境主要划分为逻辑层(AppService)和视图层(webView)。逻辑层是 JavaScript 代码尽情施展拳脚的舞台,而视图层则承担着将页面精美呈现的重任。当小程序的代码包历经一番下载之旅,成功抵达后,业务代码便会分别进驻逻辑层和渲染层,开启各自的使命。
要想提升小程序的加载性能,有一个核心要点至关重要,甚至可以说是提升性能的关键密码,那就是控制小程序包的大小,这可是微信官方都着重强调的方法。
提升用户体验最立竿见影的手段,无疑就是严格把控小程序包的大小。一般而言,大约 1M 大小的代码包,下载所耗费的时间差不多在 1 秒左右。
下面为你详细介绍控制包大小的实用措施:
1.精简代码:对代码进行全面压缩,同时仔细清理那些不再发挥作用的无用代码,让代码更加简洁高效。
2.优化样式资源:确保 wxss 样式的高覆盖率,尽量减少甚至杜绝引入那些未被使用的样式。要根据实际需求精准引入 wxss 资源,因为如果小程序中存在大量闲置的样式,会让小程序包的体积变得臃肿,进而拖慢加载速度。
3.合理处理图片:把图片存放到 CDN 上,借助 CDN 的强大优势来提升图片加载效率。
4.精准打包:在打包过程中,务必避免将那些不会被使用的文件也一并打包进去,做到精准聚焦。
5.采用分包策略:巧妙运用分包策略,对代码进行合理拆分,让小程序的加载更加灵活高效。
6.分包预加载:提前进行分包预加载,让用户在使用小程序时能够更快地获取所需资源。
7.考虑独立分包:虽然独立分包对版本有一定要求,但如果条件允许,也可以作为优化的一个方向。
除了控制包的大小之外,对异步请求进行优化同样不可小觑。以下是一些优化异步请求的实用技巧:
1.提前发起请求:在 onLoad 阶段就果断发起请求,无需等到 ready 阶段,为数据获取争取宝贵的时间。
2.利用缓存机制:将请求结果妥善存放在缓存中,下次需要使用时可以直接从缓存中获取,避免重复请求。
3.展示骨架图:在请求过程中,可以先展示骨架图,让用户在等待数据的过程中也能感受到小程序的流畅性。
4.先反馈再请求:例如点赞按钮,可以先改变按钮的样式,给用户一个即时的反馈,然后再发起异步请求,提升用户体验。
接下来,我们把目光转向提升渲染性能。在小程序的渲染世界里,setData 扮演着重要的角色。每调用一次 setData,就意味着逻辑层要与渲染层进行一次通讯。不过,这个通讯过程并非直接传递到 webView,而是要经过 native 层,这就使得通讯的开销变得相当可观。
当渲染层接收到通讯信息后,还需要重新进行渲染操作。所以,每一次 setData 的调用都会带来两方面的开销:一是通讯开销,二是 webview 更新的开销。
下面来深入了解一下 setData 的工作原理:小程序的视图层目前借助 WebView 作为渲染的载体,而逻辑层则由独立的 JavascriptCore 充当运行环境。从架构层面来看,WebView 和 JavascriptCore 都是相对独立的模块,它们之间并没有直接的数据共享通道。当前,视图层和逻辑层之间的数据传输,实际上是通过两边提供的 evaluateJavascript 来实现的。也就是说,用户要传输的数据,需要先将其转换为字符串的形式,然后把转换后的数据内容精心拼接成一份 JS 脚本,最后通过执行 JS 脚本的方式传递到两边独立的环境中。
然而,evaluateJavascript 的执行会受到诸多因素的影响,这就导致数据到达视图层并非实时的。由于小程序运行在逻辑线程与渲染线程之上,setData 的调用会将数据从逻辑层传递到渲染层,如果数据量过大,就会不可避免地增加通信时间,影响小程序的渲染性能。
在小程序的数据传输过程中,逻辑层会施展一个重要的“魔法”——执行 `JSON.stringify` 操作。这个操作就像是一个严格的筛选官,会把 `setData` 数据里那些无法进行传输的部分统统去除,之后才将“纯净”的数据发送给视图层。
与此同时,逻辑层还会做一件贴心的事,它会把 `setData` 所设置的数据字段和 `data` 进行合并。这样一来,开发者就能通过 `this.data` 轻松读取到变更之后的数据,就像在自己的“数据宝库”中精准找到所需物品一样方便。
为了优化小程序的性能,我们可以从以下几个方面入手:
1. 精准把控 `setData` 数据量:如果某个数据并不会对渲染层产生影响,那就没必要把它放在 `setData` 里面。就像装修房子时,只把真正需要展示的物品摆放在显眼位置,而那些无关紧要的杂物就不用拿出来占用空间。
2. 合并 `setData` 请求:尽可能地将 `setData` 的请求进行合并,以此减少通讯的次数。这就好比去超市购物,把需要购买的物品列个清单,一次性买齐,而不是一趟趟地来回跑,既节省时间又提高效率。
3. 实现列表的局部更新
- 在一个列表里有 `n` 条数据,并且采用上拉加载更多的方式。假设此时要对其中某一条数据进行点赞操作,还希望能及时看到点赞效果。一种做法是采用 `setData` 全局刷新,点赞完成后,重新获取数据,然后再次进行全局重新渲染。这种方法的优点显而易见,操作方便又快捷。但缺点也很突出,用户体验会变得极其糟糕。当用户浏览了 100 多条数据后,重新渲染的工作量太大,页面可能会出现空白期,就像看电影时突然黑屏一样影响体验。
- 如果采用局部刷新的方式,就可以避免上述问题。把点赞的 `id` 传递过去,明确知道点赞的是哪一条数据,然后重新获取数据,找到对应 `id` 那条数据的下标(要知道,`index` 是不会改变的),最后使用 `setData` 进行局部刷新。示例代码如下:
this.setData({
'list[' + index + ']': newList[index]
})
4. 谨慎处理后台页面的 JS
- 在小程序中可能存在多个页面,虽然每个页面都有自己独立的 `webview`(渲染层),但它们却共享同一个 JS 运行环境。这就好比一群人在一个大房子里生活,虽然每个人都有自己的房间,但有些公共资源是大家共用的。
- 当你从一个页面(假设是 A 页面)跳转到另一个页面(假设是 B 页面)时,A 页面的定时器等 JS 操作并不会自动停止,而是依然在默默运行,并且还会抢占 B 页面的资源。在 H5 环境中,当我们跳转到其他页面时,老页面的 JS 环境会自动销毁,定时器等也会随之消失,我们无需操心老页面中还有哪些 JS 代码可能会继续执行。但在小程序里,我们必须手动“清理”这些代码,就像离开房间时要随手关灯、关电器一样,避免资源浪费。
5. 警惕 `onPageScroll` 事件
- `pageScroll` 事件本质上也是一次通讯,是从 `webview` 层向 JS 逻辑层发起的通讯。而且这次通讯的开销可不小,如果这个事件被频繁调用,并且回调函数中还有复杂的 `setData` 操作,那么小程序的性能就会变得很差。这就好比一条繁忙的道路,车流量太大,还时不时有大型车辆缓慢行驶,交通自然就会拥堵不堪。
5、优先选用小程序组件
在开发小程序时,应尽可能地运用小程序组件。自定义组件具备独特的更新机制,其更新操作仅在组件内部开展,不会受到页面其他部分内容的干扰。
举个例子,在处理一些运营活动的定时模块时,我们可以把它单独提取出来,制作成一个定时组件。这样一来,当定时组件进行更新时,页面上的其他元素能够不受影响地保持正常状态,不会因为定时组件的更新而出现异常或卡顿。
此外,各个组件都拥有属于自己的独立逻辑空间。这意味着每个组件都有其专属的数据,并且可以独立进行 setData
调用。各个组件就像一个个独立运行的小单元,彼此之间互不干扰,既保证了功能的独立性,又提高了小程序整体的可维护性和性能。