小程序原理
小程序是双线程应用,提高了渲染速度。通过两个 webview 来达到安全沙箱的目的,线程之间的通信则是通过 jsBridge 底层。
基本概念
逻辑层:业务处理
- 加载js => 业务、生命周期(Page、App)、事件、数据加载等处理
- 单独的线程: iframe(ios/jscore,安卓/v8)
- 只有一个逻辑层来维护一个 App 实例(多个 Page 实例维护在一个 App 实例中)
- 更新视图层数据:setData
渲染层:页面的基本结构、样式
- 单独的线程: webkit(iframe)
- wxml:视图内容 => html
- wxss: 视图样式 => css
- 多个视图层(一个 wxml => html 就是一个视图层)
- wxs:
运行时流程
逻辑层
- 提供注册实例/页面方法 App()、Page()
- 提供通信能力,js => wxml
- 生命周期维护能力
- 调用微信原生API能力
- 执行过程
视图层
- 事件处理
- 提供通信能力,wxml => js
- 数据渲染到页面能力
- webComponent / render能力
- 执行过程
与传统 web 页面显示流程的对比
传统 web 页面显示需要经历一下几个步骤
- webview 初始化
- 加载 HTML, CSS, JS
- 编译 JS
- Render 计算
- DOM Path
小程序架构运行流程
webview部分
- webview 初始化
- 加载 HTML,CSS, JS (经过拆分后,体积大幅度减小)
- 编译 JS
- 等待页面需要的数据
- 反序列化数据
- 执行 Patch
- 渲染页面
- 等待更多消息
jsCore部分
- 初始化
- 加载框架 js 代码
- 编译 js
- 加载业务逻辑 js 代码
- 编译 js
- 计算首屏虚拟 DOM 结构
- 序列化数据,传输
- 等待 webview 消息,或者 Native 消息
小程序的优点和缺点
优点
- 渲染进程和逻辑进程分离,并行处理:
- 加速首屏渲染速度;避免单线程模型下,js 运算时间过长,UI 出现卡顿。
- 完全采用数据驱动的方式,不能直接操作 DOM,避免低质量的代码。
缺点
- 不能灵活操作 DOM,无法实现较为复杂的效果
- 部分和 NA 相关的视图有使用限制,如微信的 scrollView 内不能有 textarea 和原生组件
- 页面大小、打开页面数量都受到限制
- 需要单独开发适配,不能复用现有代码资源。
启动加载性能优化
启动加载原理
小程序启动主要分为逻辑层的启动和视图层的启动。逻辑层执行js代码逻辑,视图层以 webview 为载体,完成页面内容的渲染和更新。
小程序启动前,客户端会对小程序的基础环境进行预加载,提升小程序加载的速度。在用户打开小程序的时,会首先进行代码包的下载,下载完成后分别在逻辑层和视图层注入执行开发者的业务代码,最终将执行结果聚合渲染出首屏内容。
启动过程三个阶段:
- 资源准备,代码包的下载和校验工作。
- 业务代码的注入以及落地页首次渲染,进行业务代码的注入和执行,等待首次渲染完成。
- 落地页请求时的网络加载状态
上面三个过程基本与传统 web 应用类似。
控制代码包大小
从加载原理上来看,代码包的大小最直接影响整个小程序加载启动性能。提升小程序的启动加载性能,最直接有效的就是减少代码包的大小。
- 开启“代码压缩”的选项。
- 编码时规范,减少层级等方式来做。
- 即时清理废弃的代码,尤其是比较大的第三方库,以及一些不使用的图片等资源文件。
- 减少本地图片等资源文件,必要时使用网络图片代码。
以上手段主要通过code的方式来减少代码量。
分包
分包加载
根据业务场景,将用户访问率高的页面放在主包里,将访问率低的页面放入子包里,按需加载; 在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。
独立分包
独立分包是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。当用户进入普通分包或主包内页面时,主包才会被下载。 可以按需将某些具有一定功能独立性的页面配置到独立分包中。当小程序从普通的分包页面启动时,需要首先下载主包;而独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度。
分包预下载
开发者可以通过配置,在进入小程序某个页面时,由框架自动预下载可能需要的分包,提升进入后续分包页面时的启动速度。对于独立分包,也可以预下载主包。
首屏优化加载
提前请求
数据请求并不依赖页面结构完整,可以在页面加载时或代码注入时即 在页面 onload 就发起,而不需要等待页面渲染完成。用户等待请求返回的时间就会进一步缩短。
利用缓存
利用storage API 对请求结果请进缓存,二次启动时,直接用缓存数据完成渲染。同时,即使在无网环境下,用户也可以使用小程序的部分功能。
避免白屏
使用骨架屏幕,请求过程中,在页面中先展示一个基础的骨架和结合已有的数据进行展示。
渲染性能优化
小程序更新视图数据的通信流程
每当小程序视图数据需要更新时,逻辑层会调用小程序宿主环境提供的 setData 方法将数据从逻辑层传递到视图层,经过一系列渲染步骤之后完成UI视图更新。完整的通信流程如下:
- 小程序逻辑层调用宿主环境的 setData 方法。
- 逻辑层执行 JSON.stringify 将待传输数据转换成字符串并拼接到特定的JS脚本,并通过evaluateJavascript 执行脚本将数据传输到渲染层。
- 渲染层接收到后, WebView JS 线程会对脚本进行编译,得到待更新数据后进入渲染队列等待 WebView 线程空闲时进行页面渲染。
- WebView 线程开始执行渲染时,待更新数据会合并到视图层保留的原始 data 数据,并将新数据套用在WXML片段中得到新的虚拟节点树。经过新虚拟节点树与当前节点树的 diff 对比,将差异部分更新到UI视图。同时,将新的节点树替换旧节点树,用于下一次重渲染。
从架构上,逻辑层和视图层无法直接共享数据的,数据传输是一次跨进程的通信,会有一定的通信开销,这一开销与传输的数据量正相关。
正确使用 setData
- 避免在 data 中放置与渲染无关的数据,只在 data 中放置与页面渲染相关的数据。
- 避免使用 setData 一次性传输大量数据,只对发生变化的数据进行 setData。
- 不要在短时间内连续的频繁调用 setData,对连续的 setData 尽可能的进行合并,做切片。
使用自定义组件
自定义组件除了有利于代码复用,提升开发效率外,还可以有效的提升页面局部频繁更新时的性能。自定义组件的更新只在组件内部进行,不受页面其他部分内容的影响,可以大大降低页面更新的开销。
在页面引用自定义组件后,当初始化页面时,Exparser 会在创建页面实例的同时,也会根据自定义组件的注册信息进行组件实例化,然后根据组件自带的 data 数据和组件WXML,构造出独立的 Shadow Tree ,并追加到页面 Composed Tree 。创建出来的 Shadow Tree 拥有着自己独立的逻辑空间、数据、样式环境及setData调用:
减少数据结构的嵌套层数
时间开销大体上与节点树中节点的总量成正比例关系。因而减少 WXML 中节点的数量可以有效降低初始渲染和重渲染的时间开销,提升渲染性能。
初始渲染时,将初始数据套用在对应的 WXML 片段上生成节点树。最后根据节点树包含的各个节点,在界面上依次创建出各个组件。
key 值在列表渲染中的作用
与各大框架的 key 值作用类似