小程序性能优化~~~(总)
小程序底层架构
微信小程序是大前端跨平台技术的其中一种产物,与当下其他热门的技术 React Native、Weex、Flutter 等不同,小程序的最终渲染载体依然是浏览器内核,而不是原生客户端。
而对于传统的网页来说,UI 渲染和 JS 脚本是在同一个线程中执行,所以经常会出现 “阻塞” 行为。微信小程序基于性能的考虑,启用了双线程模型:
视图层:也就是 webview 线程,负责启用不同的 webview 来渲染不同的小程序页面;
逻辑层:一个单独的线程执行 JS 代码,可以控制视图层的逻辑;
然而,任何线程间的数据传输都是有延时的,这意味着逻辑层和视图层间通信是异步行为。除此之外,微信为小程序提供了很多客户端原生能力,在调用客户端原生能力的过程中,微信主线程和小程序双线程之间也会发生通信,这也是一种异步行为。这种异步延时的特性会使运行环境复杂化,稍不注意,就会产出效率低下的编码。
作为小程序开发者,我们常常会被下面几个问题所困扰:
小程序启动慢;白屏时间长;页面渲染慢;运行内存不足;
接下来提出一些实质性的优化方案,但是还是要具体问题具体分析。
优化技巧
1、剔除无用的代码逻辑。
2、减少代码包中的静态资源文件。
3、复杂逻辑交给服务端处理之后再返回。
4、组件和逻辑复用,减少重复代码。
5、分包加载:小程序启动时只会下载主包/独立分包,启用分包可以有效减少下载时间。(独立)分包需要遵循一些原则,详细的可以看官方文档:
6、部分页面 h5 化:小程序提供了 web-view 组件,支持在小程序环境内访问网页。当实在无法在小程序代码包中腾出多余空间时,可以考虑降级方案 —— 把部分页面 h5 化。
7、启用本地缓存:小程序提供了读写本地缓存的接口,数据存储在设备硬盘上。由于本地 I/O 读写(毫秒级)会比网络请求(秒级)要快很多,所以在用户访问页面时,可以优先从缓存中取上一次接口调用成功的数据来渲染视图,待网络请求成功后再覆盖最新数据重新渲染。除此之外,缓存数据还可以作为兜底数据,避免出现接口请求失败时页面空窗,一石二鸟。
8、数据预拉取:小程序官方为开发者提供了一个在小程序冷启动时提前拉取第三方接口的能力:数据预拉取。
9、跳转时预拉取:为了尽快获取到服务端数据,比较常见的做法是在页面 onLoad 钩子被触发时发起网络请求,但其实这并不是最快的方式。从发起页面跳转,到下一个页面 onLoad 的过程中,小程序需要完成一些环境初始化及页面实例化的工作,耗时大概为 300 ~ 400 毫秒。实际上,我们可以在发起跳转前(如 wx.navigateTo 调用前),提前请求下一个页面的主接口并存储在全局 Promise 对象中,待下个页面加载完成后从 Promise 对象中读取数据即可。
10、如果开启了分包加载能力,在用户访问到分包内某个页面时,小程序才会开始下载对应的分包。当处于分包下载阶段时,页面会维持在 “白屏” 的启动态,这用户体验是比较糟糕的。幸好,小程序提供了 分包预下载 能力,开发者可以配置进入某个页面时预下载可能会用到的分包,避免在页面切换时僵持在 “白屏” 态。
11、非关键渲染数据延迟请求:在初始化首页时,小程序会发起一个聚合接口请求来获取主体模块的数据,而非主体模块的数据则从另一个接口获取,通过拆分的手段来降低主接口的调用时延,同时减少响应体的数据量,缩减网络传输时间。
12、分屏渲染:先渲染可视区域里面的内容,然后再渲染非可视区域里面的内容,或者当用户滚动到视野范围内再渲染。
13、接口聚合,请求合并:超出并发限制数目的 HTTP 请求将会被阻塞,需要在队列中等待前面的请求完成,从而一定程度上增加了请求时延。因此,对于职责类似的网络请求,最好采用节流的方式,先在一定时间间隔内收集数据,再合并到一个请求体中发送给服务端。
14、图片资源优化:使用 WebP 格式,WebP 是 Google 推出的一种支持有损/无损压缩的图片文件格式,得益于更优的图像数据压缩算法,其与 JPG、PNG 等格式相比,在肉眼无差别的图片质量前提下具有更小的图片体积(据官方说明,WebP 无损压缩体积比 PNG 小 26%,有损压缩体积比 JPEG 小 25-34%)。
15、骨架屏:一方面,我们可以从降低网络请求时延、减少关键渲染的节点数这两个角度出发,缩短完成 FMP(首次有效绘制)的时间。另一方面,我们也需要从用户感知的角度优化加载体验。
16、只把与界面渲染相关的数据放在 data 中,setData 传输的数据量越多,线程间通信的耗时越长,渲染速度就越慢。根据微信官方测得的数据,传输时间和数据量大体上呈正相关关系。
17、应用层的数据 diff,组件层面的 diff,这个虽然自己实现的话比较复杂,推荐使用市面上一些比较流行的多端框架:Taro或者Mpx。
18、去掉不必要的事件绑定,去掉不必要的节点属性。当用户事件(如 Click、Touch 事件等)被触发时,视图层会把事件信息反馈给逻辑层,这也是一个线程间通信的过程。但,如果没有在逻辑层中绑定事件的回调函数,通信将不会被触发。组件节点支持附加自定义数据 dataset(见下面例子),当用户事件被触发时,视图层会把事件 target 和 dataset 数据传输给逻辑层。那么,当自定义数据量越大,事件通信的耗时就会越长,所以应该避免在自定义数据中设置太多数据。
19、事件总线,替代组件间数据绑定的通信方式,WXML 数据绑定是小程序中父组件向子组件传递动态数据的较为常见的方式,在此过程中,不可避免地需要经历一次组件的 setData 调用方可完成任务,这就会产生线程间的通信。通过事件总线(EventBus),也就是发布/订阅模式,来完成由父向子的数据传递。子组件被创建时事先监听数据下发事件,当父组件获取到数据后触发事件把数据传递给子组件,这整个过程都是在小程序的逻辑层里同步执行,比数据绑定的方式速度更快。
20、onPageScroll 事件回调使用节流。