小程序原理 及 优化
小程序使用的是双线程
在这种架构中,视图层使用
WebView
作为渲染载体,而逻辑层是由独立的JavascriptCore
作为运行环境 >
两者都是独立的模块,并不具备数据直接共享的通道。视图层和逻辑层的数据传输,要由
Native
的JSBrigde
做中转
小程序的启动过程
1、小程序初始化: 微信初始化小程序环境:包括
Js 引擎
和WebView
进行初始化,并注入公共基础库。 这步是微信做的,在用户打开小程序之前就已经准备好了,是小程序运行环境预加载。2、下载小程序代码包 对小程序业务代码包进行下载:下载的不是小程序的源代码,而是编译、压缩、打包之后的代码。
3、加载小程序代码包 对下载完成对代码包进行注入执行。 此时,
app.js
、页面所在的Js 文件
和所有其他被require 的 Js 文件
会被自动执行一次,小程序基础库会完成所有页面的注册。4、初始化小程序首页 拉取数据,从逻辑层传递到视图层,进行渲染
setData 的工作原理
1、调用
setData
方法;2、逻辑层会执行一次
JSON.stringify
来去除掉setData
数据中不可传输
的部分,将待传输数据转换成字符串并拼接到特定的JS脚本
, 并通过evaluateJavascript
执行脚本将数据传输到渲染层。3、渲染层接收到后,
WebView JS 线程
会对脚本进行编译,得到待更新数据后进入渲染队列等待WebView 线程
空闲时进行页面渲染。4、
WebView 线程
开始执行渲染时,将data
和setData
数据套用在WXML
片段上,得到一个新节点树。经过新虚拟节点树与当前节点树的diff
对比,将差异部分更新到UI
视图。最后,将setData
数据合并到data
中,并用新节点树替换旧节点树,用于下一次重渲染
小程序官方性能指标
1、首屏时间不超过
5 秒
;2、渲染时间不超过
500ms
;3、每秒调用
setData
的次数不超过20 次
;4、
setData
的数据在JSON.stringify
后不超过256kb
;5、页面
WXML
节点少于1000 个
,节点树深度少于 30 层
,子节点数
不大于60 个
;6、所有网络请求都在 1 秒内返回结果;
小程序优化
1、分包并且使用
分包预加载(通过配置 preloadRule
) 将访问率低的页面放入子包里,按需加载;启动时需要访问的页面及其依赖的资源文件应放在主包中。 不需要等到用户点击到子包页面后在下载子包,而是可以根据后期数据,做子包预加载,将用户在当先页可能点击的子包页面先加载,当用户点击后直接跳转;可以根据用户网络类型来判断的,当用户处于网络条件好时才预加载;
2、采用独立分包技术(感觉开普勒黄金流程源码可以独立分包)
主包+子包的方式,,如果要跳到子包里,还是会加载主包然后加载子包;采用独立分包技术,区别于子包,和主包之间是无关的,在功能比较独立的子包里,使用户只需下载分包资源;
3、异步请求可以在页面onLoad
就加载
4、注意利用缓存
利用storage API
, 对变动频率比较低的异步数据进行缓存,二次启动时,先利用缓存数据进行初始化渲染,然后后台进行异步数据的更新
5、及时反馈
及时对需要用户等待的交互操作进行反馈,避免用户以为小程序卡了 先反馈,再请求。比如说,点赞的按钮,可以先改变按钮的样式,再 发起异步请求。
6、可拆分的部分尽量使用自定义组件
自定义组件的更新并不会影响页面上其他元素的更新,各个组件具有独立的逻辑空间、数据、样式环境及 setData 调用
7、避免不当的使用onPageScroll
避免在onPageScroll
中执行复杂的逻辑,避免在onPageScroll
中频繁使用setData
,避免在onPageScroll中 频繁查询几点信息(selectQuery
)
8、减少在代码包中直接嵌入的资源文件;图片放在cdn,使用适当的图片格式
9、setData 优化
(1)与界面渲染无关的数据最好不要设置在 data
中,可以考虑设置在 page
对象的其他字段下;
this.setData({ a: '与渲染有关的字符串', b: '与渲染无关的字符串' }) // 可以优化为 this.setData({ a: '与渲染有关的字符串' }) this.b = '与渲染无关的字符串' 复制代码
(2)不要过于频繁调用 setData
,将多次 setData
合并成一次 setData
调用
(3)数据通信的性能与数据量正相关,因而如果有一些数据字段不在界面中展示
且数据结构比较复杂
或包含长字符串
,则不应使用setData
来设置这些数据
(4)列表局部更新 在更新列表的某一个数据时。不要用 setData
进行全部数据的刷新。查找对应 id
的那条数据的下标(index
是不会改变的),用 setData
进行局部刷新
this.setData({ `list[${index}]` = newList[index] }) 复制代码
(5)切勿在后台页面进行setData
(就是不要再页面跳转后使用setData
) 页面跳转后,代码逻辑还在执行,此时多个webview
是共享一个js
进程;后台的setData
操作会抢占前台页面的渲染资源;
10、避免过多的页面节点数
页面初始渲染时,渲染树的构建、计算节点几何信息以及绘制节点到屏幕的时间开销都跟页面节点数量成正相关关系,页面节点数量越多,渲染耗时越长。
每次执行 setData
更新视图,WebView JS 线程
都要遍历节点树计算新旧节点数差异部分。当页面节点数量越多,计算的时间开销越大,减少节点树节点数量可以有效降低重渲染的时间开销。
11、事件使用不当
(1)去掉不必要的事件绑定(WXML
中的bind
和catch
),从而减少通信的数据量和次数; (2)事件绑定时需要传输target
和currentTarget
的dataset
,因而不要在节点的data
前缀属性中放置过大的数据
12、逻辑后移,精简业务逻辑
就比如咱们生成分享图片,再比如领取新人券的时候将是否是新人是否符合风控条件和最终领券封装为一个接口
13、数据预拉取(重要
)
小程序官方为开发者提供了一个在小程序冷启动时提前拉取第三方接口的能力 developers.weixin.qq.com/miniprogram… 预拉取能够在小程序冷启动的时候通过微信后台提前向第三方服务器拉取业务数据,当代码包加载完时可以更快地渲染页面,减少用户等待时间,从而提升小程序的打开速度
14、跳转时预拉取
可以在发起跳转前(如 wx.navigateTo
调用前),提前请求下一个页面的主接口并存储在全局 Promise
对象中,待下个页面加载完成后从 Promise
对象中读取数据即可
15、非关键渲染数据延迟请求
小程序会发起一个聚合接口请求来获取主体模块的数据,而非主体模块的数据则从另一个接口获取,通过拆分的手段来降低主接口的调用时延,同时减少响应体的数据量,缩减网络传输时间。
16、分屏渲染
在 主体模块 的基础上再度划分出 首屏模块 和 非首屏模块(比如京挑好货的猜你喜欢模块),在所有首屏模块都渲染完成后才会渲染非首屏模块和非主体模块,以此确保首屏内容以最快速度呈现
17、接口聚合,请求合并(主要解决小程序中针对 API 调用次数的限制)
在小程序中针对 API 调用次数的限制: wx.request
(HTTP 连接)的最大并发限制是 10
个; wx.connectSocket
(WebSocket 连接)的最大并发限制是 5
个;
18、事件总线,替代组件间数据绑定的通信方式
通过事件总线(EventBus
),也就是发布/订阅模式,来完成由父向子的数据传递
19、大图裁剪为多块加载
20、长列表优化
(1)不要每次加载更多的时候 都用concat
每获取到新一页数据时,就把它们concat
到list
上去,这样就会导致每次setData
时的list
越来越长越来越长,渲染速度也就越来越慢 (2)分批setData
,减少一次setData
的数量。不要一次性setData list
,而是把每一页一批一批地set Data
到这个list
中去
this.setData({ ['feedList[' + (page - 1) + ']']: newVal, }) 复制代码
(3)运用官方的 IntersectionObserver.relativeToViewport
将超出或者没进入可视区的 部分卸载掉(适用于一次加载很多的列表数据,超出了两屏高度所展示的内容)
this.extData.listItemContainer.relativeToViewport({ top: showNum * windowHeight, bottom: showNum * windowHeight }) .observe(`#list-item-${this.data.skeletonId}`, (res) => { let { intersectionRatio } = res if (intersectionRatio === 0) { console.log('【卸载】', this.data.skeletonId, '超过预定范围,从页面卸载') this.setData({ showSlot: false }) } else { console.log('【进入】', this.data.skeletonId, '达到预定范围,渲染进页面') this.setData({ showSlot: true, height: res.boundingClientRect.height }) } }) 复制代码
21、合理运用函数的防抖与节流,防止出现重复点击及重复请求出现 为避免频繁setData
和渲染,做了防抖函数,时间是600ms
作者:甘草倚半夏
链接:https://juejin.cn/post/7023671521075806244