当需要在频繁触发的用户事件(如 PageScroll 、 Resize 事件)中调用 setData ,合理的利用 函数防抖(debounce) 和 函数节流(throttle) 可以减少 setData 执行次数。
函数防抖(debounce) :函数在触发n秒后才执行一次,如果在n秒内重复触发函数,则重新计算时间。
函数节流(throttle) :单位时间内,只会触发一次函数,如果同一个单位时间内触发多次函数,只会有一次生效。
除了让开发者自觉遵循规则来减少 setData 数据传输量和执行频率之外,我们还可以自己设计一个 diff 算法,重新对 setData 进行封装,使得在 setData 执行之前,让待更新的数据与原 data 数据做 diff 对比,计算出数据差异 patch 对象,判断 patch 对象是否为空,如果为空则跳过执行更新,否则再将 patch 对象执行 setData 操作,从而达到减少数据传输量和降低执行 setData 频率的目的。
// setData重新封装成新的方法,使得数据更新前先对新旧数据做diff对比,再执行setData方法 this.update = (data) => { return new Promise((resolve, reject) => { const result = diff(data, this.data); if (!Object.keys(result).length) { resolve(null); return; } this.setData(result, () => { resolve(result); }); }); }
当然,可以直接引用这里 的现成 高性能小程序 setData diff算法
具体流程如下图:
善用自定义组件
小程序自定义组件的实现是由小程序官方设计的 Exparser 框架所支持,框架实现的自定义组件的组件模型与 Web Components 标准的 Shadow DOM 相似:
在页面引用自定义组件后,当初始化页面时, Exparser 会在创建页面实例的同时,也会根据自定义组件的注册信息进行组件实例化,然后根据组件自带的 data 数据和组件WXML,构造出独立的 Shadow Tree ,并追加到页面 Composed Tree 。创建出来的 Shadow Tree 拥有着自己独立的逻辑空间、数据、样式环境及setData调用:
基于自定义组件的 Shadow DOM 模型设计,我们可以将页面中一些需要高频执行 setData 更新的功能模块(如倒计时、进度条等)封装成自定义组件嵌入到页面中。当这些自定义组件视图需要更新时,执行的是组件自己的 setData ,新旧节点树的对比计算和渲染树的更新都只限于组件内有限的节点数量,有效降低渲染时间开销。
下图是我们在微保小程序WeDrive首页中,将倒计时模块抽取自定义组件前后的setData更新耗时对比:
从图中可以看出,使用自定义组件后,倒计时模块 setData 平均渲染耗时有了非常明显的下降,实际在低端安卓机中体验会感觉明显的更流畅。
当然,并不是使用自定义组件越多会越好,页面每新增一个自定义组件, Exparser 需要多管理一个组件实例,内存消耗会更大,当内存占用上升到一定程度,有可能导致 iOS 将部分 WKWebView 回收,安卓机体验会变得更加卡顿。因此要合理的使用自定义组件,同时页面设计也要注意不滥用标签。
总结
小程序双线程架构决定了数据通信优化会是性能优化中一个比较重要的点。而上述提到的几个优化建议只是优化渲染性能中的一部分,要想让你的页面体验变得更加丝滑,就要熟悉小程序框架的底层原理,根据小程序框架的特点,编写出“合身”的前端代码。
参考资料