编辑推荐:关注微信小程序、微信小游戏、Node.js 和 Java 实时 Bug 监控,真的是一款非常好用的 Bug 监控服务,很多大公司都在用。
项目结构
上图是微信小程序的项目结构,下图包括小程序内的各个页面,每个页面由页面结构、页面样式、页面配置、逻辑代码四部分组成。
页面结构文件为.wxml,使用微信自定义标签编写。
页面逻辑是通过 编写的。
与 CSS 文件类似,它定义页面内元素的样式。
页面内的权限等配置信息。
微信小程序技术选型
小程序的定位特点是轻量、快捷,针对这两个特点,微信在技术的选择上做了一些考虑。
界面渲染技术
缺点:无法动态打包和分发。
缺点:如果使用纯 Web 技术来渲染小程序,在一些交互比较复杂的页面上可能会遇到一些性能问题。这是因为在 Web 技术中,UI 渲染和脚本执行都是在单线程中执行的,这很容易导致一些逻辑任务占用 UI 渲染资源。
从底层渲染上看,和微信的 JS-SDK 类似,它们最终都是利用浏览器内核来渲染界面。RN 则不同,虽然是用 Web 相关技术编写的,也利用了解释型执行的特点,但是 RN 在渲染底层采用的是客户端原生渲染。我们选择了和微信类似的技术,即界面以成熟的 Web 技术渲染为主,辅以大量接口,提供丰富的客户端原生能力。同时每个小程序页面渲染方式都不一样,这样可以提供更好的交互体验,更贴近原生的体验,避免单个任务过重。
微信没有选择RN的原因
RN 支持的样式是 CSS 的子集,无法满足 Web 开发者日益增长的需求,并且改造 RN 具有相当大的成本和风险。RN 在目前的能力下还存在一些不稳定的问题,比如性能、Bug 等。RN 将所有的渲染工作都交给了客户端原生渲染,实际上一些简单的界面元素完全可以使用 Web 技术进行渲染,并且非常稳定。RN 存在一些不可预知的因素,比如之前的许可协议问题。
原生组件的渲染方式
在 上,会在该对象中注入原生方法,最终封装成兼容层,主要提供两个方法:call() 和 (on)。当开发者插入原生组件时,一般来说,组件运行时会插入到 DOM 树中,并调用客户端接口,通知客户端需要渲染原生界面的某部分。开发者后续更新组件属性时,也会调用客户端提供的更新接口,更新原生界面的某部分。
Web 渲染带来的问题及解决方案
由于浏览器的灵活性,以及浏览器功能的丰富性,会造成很多不可控的隐私泄露,所以微信提供了一个简单的JS执行环境,里面的控件也是自定义的,所以如果完全采用这个沙盒环境,就不能有任何浏览器相关的接口,只提供纯粹的解释执行环境。那么 中的特性就满足这样的条件,都启用了另外一个线程来执行。但是考虑到小程序是多线程架构,每个小程序页面都是经过不同的渲染后显示的,在这种架构下,我们不容易用某个线程来管理所有的小程序页面。得益于客户端系统的解释引擎(iOS 使用的是内置框架, 使用的是腾讯 x5 内核提供的环境),我们可以创建一个单独的线程来执行。在这个环境中执行的是小程序业务逻辑相关的代码,也就是我们一直在说的逻辑层。 所有跟界面渲染相关的任务都在线程中执行,而逻辑层代码用来控制渲染哪些界面,所以这一层当然就是所谓的渲染层。这就是小程序双线程模型的由来。
为了防止标签定义带来的一些问题,微信定制了一种标签语言WXML,这种标签语言经过编译后最终会生成Html。
渲染与逻辑分离
以上就是小程序渲染技术的选择,选择之后,由于渲染和逻辑不再在同一个浏览器执行,一个是纯 JS 环境,一个是通过渲染,所以小程序的运行环境就分为了渲染层和逻辑层,WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。
小程序的渲染层和逻辑层分别由两个线程管理:渲染层界面使用 进行渲染;逻辑层使用线程运行 JS 脚本。一个小程序有多个界面,因此渲染层有多个线程。这两个线程之间的通信会通过微信客户端进行中转,逻辑层也会通过转发发送网络请求。小程序的通信模型如图所示。
数据驱动的视图更改
在开发UI界面的过程中,程序需要维护很多的可变状态,操作相应的UI元素。随着界面越来越复杂,我们需要维护很多的可变状态,处理很多界面上的交互事件,整个程序也变得越来越复杂。通常界面视图和可变状态是相关的,如果有一个“方法”可以将状态和视图绑定在一起(当状态改变时,视图也能自动改变),那么我们就可以省去手动修改视图的工作了。
小程序的逻辑层和渲染层是两个独立的线程,在渲染层中宿主环境会将 WXML 转换成对应的 JS 对象,当逻辑层数据发生变化时,我们需要使用宿主环境提供的方法将数据从逻辑层传递到渲染层,然后对比前后的差异,将差异应用到原始 Dom 树上,渲染出正确的 UI 界面。
通过将msg数据从“ ”改为“ ”,生成的JS对象对应的节点就会发生变化。此时就可以对比前后两个JS对象,得到变化的部分,然后将这个差值应用到原始Dom树上,达到更新UI的目的。这就是“数据驱动”的原理。
事件处理
UI界面程序需要和用户进行交互,比如用户可能点击了你界面上的某个按钮,或者长按了某个区域。这种反馈应该通知到开发者的逻辑层,需要把相应的处理状态呈现给用户。由于目前的功能只是渲染,微信对事件分发做了特殊处理,将所有事件拦截并抛给逻辑层处理。
事件派发过程有事件捕获和冒泡两种机制,传给 JS 响应事件后,Dom 被修改,这些变化会反映到虚拟 Dom 中,然后进行真正的渲染。
数据通讯
小程序是基于双线程模型的,也就是说任何数据的传递都是线程间的通信,也就意味着会有一定的延迟。这不像传统的Web,当需要更新界面时,会通过调用更新接口来同步渲染UI。而在小程序架构下,这一切都会变成异步的。
异步会让各部分的运行顺序变得复杂,比如在渲染首屏的时候,逻辑层和渲染层会同时开始初始化,但是渲染层需要逻辑层的数据才能渲染界面,如果渲染层初始化工作很快完成,那么就需要等待逻辑层的指令才能进行下一步,因此逻辑层和渲染层需要有一定的机制来保证正确的时序。
在每个小程序页面的生命周期中,都会有几次页面数据通信,逻辑层将页面数据(数据和内容)发送给视图层,视图层再将用户事件反馈给逻辑层。

通过Json方式传输数据,提高性能的方法就是减少交互的数据量。
缓存机制
小程序宿主环境对不同小程序的数据缓存进行管理,不同小程序的本地缓存空间是分开的,每个小程序的缓存空间上限为10MB,如果当前缓存已达到10MB,通过wx.写入缓存会触发失败回调。
小程序本地缓存不仅通过小程序维度进行空间隔离,同时考虑到同一设备登录不同微信用户,宿主环境也对不同用户的缓存进行了隔离,避免用户之间数据隐私泄露。
由于本地缓存是保存在当前设备上的,用户更换设备后无法从其他设备读取当前设备数据,因此不建议将用户的关键信息只保存在本地缓存中,数据应放在服务端进行持久化存储。
支付宝小程序
支付宝小程序介绍
支付宝小程序的实现与微信小程序大致相同,所以这里主要关注两者的区别。
支付宝小程序目录结构
支付宝小程序业务架构图
在渲染引擎方面,支付宝小程序不仅提供了+的方式,还有+的方式,在对性能要求较高的场景下,可以选择一种渲染方式,给用户更好的体验。
运行时架构
小程序编程模型分为多个页面,每个页面都有独立的CSS和JS。实际运行时,业务逻辑的JS代码运行在独立的引擎中,每个页面的CSS运行在自己独立的引擎中。页面切换通过函数完成。
每个页面和公共引擎中的逻辑交互的方式是通过消息服务,页面的一些事件会通过这个消息通道传递到引擎运行环境,运行环境会响应事件,进行一些API调用,这些API可以调整为客户端支付宝小程序提供的一些能力,处理完之后数据会再次发送到对应的页面渲染容器中进行处理,结合数据和模板生成最终的用户界面。
支付宝小程序虚拟机隔离
通常的做法是把里面的代码运行完之后再开启另外一个线程,当需要更新DOM的时候就把事件和数据发到这个线程去执行。当业务需要向层传递大量数据,对象复杂的时候,交互的性能就会很差。因此我们针对这种情况提出了一个优化的解决方案。
解决方案将原有的JS虚拟机实例(ie)重新设计为两个部分:和。
在新的隔离模型下,内部只有一个 v8 实例,线程中也只有一个 v8 实例, 之间交互时会直接在 Heap 中创建对象,因此 可以直接读取该对象并用于 渲染,减少了对象序列化和网络传输,大大提升启动性能和渲染性能。
首屏速度优化
由于小程序的启动受生命周期控制,从首页->开始->退出->用户操作->离开首页的整个过程中,这个过程中任何一个环节都可能因为客观或主观原因而中断,从而可能导致保存的离线页面不准确,在启动时呈现给用户错误的页面。
所以对于首页离线缓存渲染的效果来说,保存页面的时机非常重要。我们为开发者提供了可配置的时机,可配置的时机有两种:渲染完成后和离开首页前。渲染完成是指首页渲染完成,在用户进行任何操作之前,将页面保存为离线缓存页面。离开首页前是指用户在首页进行一系列操作之后,在跳转至其他页面之前,将用户看到的页面保存为离线缓存页面。
出现闪屏问题是因为缓存页面和实际渲染的页面是分离的,是两个独立的页面,缓存页面是静态页面,而实际页面是js动态创建的,所以常规的做法是在创建实际页面的时候替换缓存页面,这样就会出现闪屏的情况。
为了解决这个问题,我们采用虚拟 DOM 来解决,在加载缓存页面时,将缓存页面放入初始的虚拟 DOM 中,在真实页面创建完成后,再将生成的虚拟 DOM 与缓存页面的虚拟 DOM 进行 DOM diff,将变化的内容传递给浏览器内核,渲染相应的页面。这样,只需要更新局部变化的页面内容,避免整个页面的更新,保证内容的准确性和实时性。
支付宝利用UC浏览器的核心优势
1、图片内存:针对低端机器,实施更严格的图片缓存限制,在保持性能体验的同时进一步限制图片缓存的使用;多个共享的图片缓存池;全面支持更加节省内存和大小的webp、apng等图片格式。
2、渲染内存:在不可见状态下,原生内存管理没有特殊处理,UC内核会释放不可见的渲染内存;合理的设置和调优渲染内存,可以避免滚动性能下降、内存占用过大等问题。
3.JS内存:更合理处理v8内存GC,延迟启动时full GC的执行,避免影响启动时间。
4、峰值内存管理:当系统内存不足时,会通知内核,UC内核可以在系统内存不足时释放占用非关键内存的模块,避免过度释放导致OOM、黑块。在某些OOM情况下,避免原生内核主动崩溃逻辑,内存极低时,部分功能不可用,而不是崩溃。
对我们意味着什么
增加小程序的存储,包括内存和磁盘,可以缓存一些数据,提高页面输出速度。同时磁盘的管理按照小程序账号双维度划分。
支持第三方接入后,现有的方式会导致安全和第三方行为完全不可控,可以参考微信、支付宝使用自定义标记语言进行限制标记语言,提供纯JS环境执行JS环境,JS环境只负责渲染。
参考支付宝的解决方案,在加载的时候先把旧的页面展示给用户,然后等新的页面加载完成后再计算差额然后显示。
绘制利用JS与通信将价格控件添加至布局指定区域。
所有的网络请求都由托管服务进行管理,可以更好地控制和监控网络请求。