微信小程序

小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。

技术选型

渲染界面的技术

  • 用纯客户端原生技术来渲染

缺点:无法动态打包,动态下发。

  • 用纯 Web 技术来渲染

缺点:如果我们用纯 Web 技术来渲染小程序,在一些有复杂交互的页面上可能会面临一些性能问题,这是因为在 Web 技术中,UI渲染跟 JavaScript 的脚本执行都在一个单线程中执行,这就容易导致一些逻辑任务抢占UI渲染的资源。

  • 介于客户端原生技术与 Web 技术之间的,互相结合各自特点的技术来渲染

从渲染底层来看,PhoneGap 与微信 JS-SDK 是类似的,它们最终都还是使用浏览器内核来渲染界面。而 RN 则不同,虽然是用 Web 相关技术来编写,同样是利用了 JavaScript 解释执行的特性,但 RN 在渲染底层是用客户端原生渲染的。我们选择类似于微信 JSSDK 这样的 Hybrid 技术,即界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每个小程序页面都是用不同的 WebView 去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个 WebView 的任务过于繁重。

不选择 RN 的原因?

  • RN 所支持的样式是 CSS 的子集,会满足不了 Web 开发者日渐增长的需求,而对 RN 的改造具有不小的成本和风险。
  • RN 现有能力下还存在的一些不稳定问题,比如性能、Bug 等。RN 是把渲染工作全都交由客户端原生渲染,实际上一些简单的界面元素使用 Web 技术渲染完全能胜任,并且非常稳定。
  • RN 存在一些不可预期的因素,比如之前出现的许可协议问题

原生组件的渲染方式

在安卓则是往 WebView 的 window 对象注入一个原生方法,最终会封装成 WeiXinJSBridge 这样一个兼容层,主要提供了 调用(invoke)和 监听(on)这两种方法。开发者插入一个原生组件,一般而言,组件运行的时候被插入到 DOM 树中,会调用客户端接口,通知客户端在哪个位置渲染一块原生界面。在后续开发者更新组件属性时,同样地,也会调用客户端提供的更新接口来更新原生界面的某些部分。

网页渲染的问题与解决方案

  • 提供干净纯粹的 JavaScript 执行环境

由于 JavaScript 的灵活性和浏览器的功能丰富,会导致很多不可控的隐私,因此,微信提供了一个单纯的JS执行环境,通过对于其中的控件也进行了自定义。因此完全采用这个沙箱环境不能有任何浏览器相关接口,只提供纯 JavaScript 的解释执行环境,那么像 HTML5 中的 ServiceWorker、WebWorker 特性就符合这样的条件,这两者都是启用另一线程来执行 JavaScript。但是考虑到小程序是一个多 WebView 的架构,每一个小程序页面都是不同的 WebView 渲染后显示的,在这个架构下我们不好去用某个 WebView 中的 ServiceWorker 去管理所有的小程序页面。得益于客户端系统有 JavaScript 的解释引擎(在 iOS 下是用内置的 JavaScriptCore 框架,在安卓则是用腾讯 x5 内核提供的 JsCore 环境),我们可以创建一个单独的线程去执行 JavaScript,在这个环境下执行的都是有关小程序业务逻辑的代码,也就是我们前面一直提到的逻辑层。而界面渲染相关的任务全都在 WebView 线程里执行,通过逻辑层代码去控制渲染哪些界面,那么这一层当然就是所谓的渲染层。这就是小程序双线程模型的由来。

  • 自定义标签

为了防止标签定义带来的一些问题,微信自定义了一套标签语言 WXML,这套标签语言经过编译之后,最终会生成 HTML。

渲染与逻辑分离

小程序的渲染层和逻辑层分别由 2 个线程管理:

  • 渲染层:渲染层的界面使用了 WebView 进行渲染
  • 逻辑层:逻辑层采用 JSCore 线程运行 JavaScript 脚本

一个小程序存在多个界面,所以渲染层存在多个 WebView 线程,这两个线程的通信会经由微信客户端(下文中也会采用 Native 来代指微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发,小程序的通信模型下图所示。

小程序渲染层和逻辑层通信模型

数据驱动

在开发 UI 界面过程中,程序需要维护很多变量状态,同时要操作对应的 UI 元素。随着界面越来越复杂,我们需要维护很多变量状态,同时要处理很多界面上的交互事件,整个程序变得越来越复杂。通常界面视图和变量状态是相关联的,如果有某种 方法 可以让状态和视图绑定在一起(状态变更时,视图也能自动变更),那我们就可以省去手动修改视图的工作。

小程序的逻辑层和渲染层是分开的两个线程。在渲染层,宿主环境会把 WXML 转化成对应的 JS 对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的 setData 方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的 DOM 树上,渲染出正确的 UI 界面。

小程序事件触发通信原理

通过 setDatamsg 数据从 “Hello World” 变成 “Goodbye”,产生的 JS 对象对应的节点就会发生变化,此时可以对比前后两个 JS 对象得到变化的部分,然后把这个差异应用到原来的 DOM 树上,从而达到更新 UI 的目的,这就是 数据驱动 的原理。

小程序DIFF流程小程序更新数据流程

事件的处理

UI 界面的程序需要和用户互动,例如用户可能会点击你界面上某个按钮,又或者长按某个区域,这类反馈应该通知给开发者的逻辑层,需要将对应的处理状态呈现给用户。由于 WebView 现在具备的功能只是进行渲染,因此对于事件的分发处理,微信进行了特殊的处理,将所有的事件拦截后,丢到逻辑层交给 JavaScript 进行处理。

事件的派发处理,具备事件捕获和冒泡两种机制。通过 Native 传递给 JSCore,通过 JS 来响应响应的事件之后,对 DOM 进行修改,改动会体现在虚拟 DOM 上,然后再进行真实的渲染。

小程序事件模型

数据通信

小程序是基于 双线程模型,那就意味着任何数据传递都是线程间的通信,也就是都会有一定的延时。这不像传统 Web 那样,当界面需要更新时,通过调用更新接口 UI 就会同步地渲染出来。在小程序架构里,这一切都会变成异步。

异步会使得各部分的运行时序变得复杂一些。比如在渲染首屏的时候,逻辑层与渲染层会同时开始初始化工作,但是渲染层需要有逻辑层的数据才能把界面渲染出来,如果渲染层初始化工作较快完成,就要等逻辑层的指令才能进行下一步工作。因此逻辑层与渲染层需要有一定的机制保证时序正确,

在每个小程序页面的生命周期中,存在着若干次页面数据通信。逻辑层向视图层发送页面数据(datasetData 的内容),视图层向逻辑层反馈用户事件。

小程序数据通信

通过 JSON 的方式进行数据的传递,提高性能的方式就是减少交互的数据量。

缓存机制

小程序宿主环境会管理不同小程序的数据缓存,不同小程序的本地缓存空间是分开的,每个小程序的缓存空间上限为 10MB,如果当前缓存已经达到 10MB,再通过 wx.setStorage 写入缓存会触发 fail 回调。

小程序的本地缓存不仅仅通过小程序这个维度来隔离空间,考虑到同一个设备可以登录不同微信用户,宿主环境还对不同用户的缓存进行了隔离,避免用户间的数据隐私泄露。

由于本地缓存是存放在当前设备,用户换设备之后无法从另一个设备读取到当前设备数据,因此用户的关键信息不建议只存在本地缓存,应该把数据放到服务器端进行持久化存储。

运行时状态

运行环境

微信小程序运行在多种平台上:iOS(iPhone/iPad)微信客户端、Android 微信客户端、PC 微信客户端、Mac 微信客户端和用于调试的微信开发者工具。

各平台脚本执行环境以及用于渲染非原生组件的环境是各不相同的:

  • 在 iOS 上,小程序逻辑层的 JavaScript 代码运行在 JavaScriptCore 中,视图层是由 WKWebView 来渲染的,环境有 iOS 12、iOS 13 等;
  • 在 Android 上,小程序逻辑层的 JavaScript 代码运行在 V8 中,视图层是由自研 XWeb 引擎基于 Mobile Chrome 内核来渲染的;
  • 在 开发工具上,小程序逻辑层的 JavaScript 代码是运行在 NW.js 中,视图层是由 Chromium Webview 来渲染的。

运行机制

前台/后台状态

小程序启动后,界面被展示给用户,此时小程序处于 前台 状态。

当用户点击右上角胶囊按钮关闭小程序,或者按了设备 Home 键离开微信时,小程序并没有完全终止运行,而是进入了后台状态,小程序还可以运行一小段时间。

当用户再次进入微信或再次打开小程序,小程序又会从后台进入 前台。但如果用户很久没有再进入小程序,或者系统资源紧张,小程序可能被 销毁,即完全终止运行。

小程序启动

这样,小程序启动可以分为两种情况,一种是 冷启动,一种是 热启动

  • 冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动。
  • 热启动:如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。

小程序销毁时机

通常,只有当小程序进入后台一定时间,或者系统资源占用过高,才会被销毁。具体而言包括以下几种情形:

  • 当小程序进入后台,可以维持一小段时间的运行状态,如果这段时间内都未进入前台,小程序会被销毁。
  • 当小程序占用系统资源过高,可能会被系统销毁或被微信客户端主动回收。
    • 在 iOS 上,当微信客户端在一定时间间隔内连续收到系统内存告警时,会根据一定的策略,主动销毁小程序,并提示用户 运行内存不足,请重新打开该小程序。具体策略会持续进行调整优化。
    • 建议小程序在必要时使用 wx.onMemoryWarning 监听内存告警事件,进行必要的内存清理。

与普通网页开发的区别

小程序的主要开发语言是 JavaScript ,小程序的开发同普通的网页开发相比有很大的相似性。对于前端开发者而言,从网页开发迁移到小程序的开发成本并不高,但是二者还是有些许区别的。

​网页 开发渲染线程脚本线程互斥 的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在 不同的线程 中。网页开发者可以使用到各种浏览器暴露出来的 DOM API,进行 DOM 选中和操作。而如上文所述,小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的 DOM API 和 BOM API。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery、 Zepto 等,在小程序中是无法运行的。同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。

​网页开发者需要面对的环境是各式各样的浏览器,PC 端需要面对 IE、Chrome、QQ 浏览器等,在移动端需要面对 Safari、Chrome 以及 iOS、Android 系统中的各式 WebView。而小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的微信客户端,以及用于辅助开发的小程序开发者工具,小程序中三大运行环境也是有所区别的,

运行环境逻辑层渲染层
iOSJavaScriptCoreWKWebView
安卓V8Chromium 定制内核
小程序开发者工具NWJSChrome WebView

​网页开发者在开发网页的时候,只需要使用到浏览器,并且搭配上一些辅助工具或者编辑器即可。小程序的开发则有所不同,需要经过申请小程序帐号、安装小程序开发者工具、配置项目等等过程方可完成。


参考资料: