一、微信小程序的技术架构
在浏览器中,Javascript采用的是单线程模型,为了提升渲染效率和减少脚本错误的产生,浏览器GUI渲染进程和JS进程是互斥的。
- JS线程拥有修改DOM的能力,在GUI线程渲染未生成render树时,修改DOM无疑会报错
- GUI线程在渲染完成后,JS线程存在使GUI线程重新渲染的可能,使得渲染效率低下
这就导致一些逻辑抢占UI渲染的资源,这个问题可以通过Web Worker 解决。
小程序的宿主是微信,在这个环境下要独立迭代并保证安全独立沙箱、Native等特性等的话,需要在iframe运行,并引入额外的编译器和Web Worker保证线程安全,Worker 线程负责计算,将结果通过 postMessage 传递给主线程,主线程负责渲染。
这样的话小程序的性能就大大降低,所以在微信小程序中,两者是分开的,在各个运行环境及渲染非原生组件的环境也是不同的:
运行环境 | 逻辑层 | 渲染层 |
---|---|---|
iOS / Mac | JSCore | WKWebView |
Android | V8 | Chromium定制内核 |
小程序开发者工具 | NWJS | Chrome WebView |
PC端(window) | Chrome内核 | Chrome内核 |
这里可以看到小程序的运行环境分成 渲染层(View)和 逻辑层(App Service)
- 渲染层:界面使用了WebView 进行渲染,小程序存在多个界面,所以渲染层存在多个WebView线程
- 逻辑层:采用JsCore线程运行JS脚本
这就类似于JSSDK的Hybird技术,界面用Web渲染,原生提供接口开放客户端的原生能力。那么在小程序中这两个进程是怎么通信的呢?如下图所示,渲染层和逻辑层的通信是由微信客户端(Native)中转,逻辑层的网络请求也是由Native转发。
二、逻辑层到底是什么?
微信文档: 小程序开发框架的逻辑层使用 JavaScript 引擎为小程序提供开发者 JavaScript 代码的运行环境以及微信小程序的特有功能。逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。开发者写的所有代码最终将会打包成一份 JavaScript 文件,并在小程序启动的时候运行,直到小程序销毁。这一行为类似 ServiceWorker,所以逻辑层也称之为 App Service。
总而言之逻辑层是一个只能够运行 JavaScript ,不提供 DOM/BOM 操作的沙箱环境。
为了保障安全性和解决卡顿,逻辑层主要是:
- 缺少相关的 DOM API 和 BOM API,阻止开发者不安全的操作
- web worker来做js运行
- js bridge通知渲染层WebView更新UI
逻辑层的核心JSCore是什么呢?这里还需引申下WebKit
WebKit是一个开源的浏览器网页排版引擎,包含WebCore排版引擎和JSCore引擎。WebKit是Sfari、Chromium等浏览器的排版引擎
其中WebCore是核心的渲染引擎,JavascriptCore是WebKit默认内嵌的JS引擎。Google Chrome的V8、ReactNative和iOS平台上的Javascript引擎大都是内嵌JSCore的。
JSCore的目的是JS解释器,给App提供了JS可以解释执行的运行环境与资源。让基于JSCore的Hybrid(Bridge)能够相互调用。如下图,小程序的JSCore相比于浏览器缺少了DOM/BOM,相比于Node缺少了Native的直接调用。
三、逻辑和渲染层通信
在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面
小程序的双线程模型不同于单线程,逻辑层和渲染层的数据交互需要通过JSBridge,二者是通过发布订阅来实现数据的双向绑定,这样在逻辑层调用setData来改变 Model 层的数据就能够实现视图数据的异步更新。
比如说如下的小程序页面
<view class="container">
<view class="userinfo">
<text class="user-nickname">{{userNickName}}</text>
</view>
</view>
微信小程序通过wcc工具编译wxml为js,得到 Virtual DOM 结构
{
"tag": "wx-page",
"children": [
{
"tag": "wx-view",
"attr": {
"class": "container"
},
"children": [
{
"tag": "wx-view",
"attr": {
"class": "userinfo"
},
"children": [
{
"tag": "wx-text",
"attr": {
"class": "user-nickname"
},
"children": [
""
],
"raw": {},
"generics": {}
}
],
"raw": {},
"generics": {}
}
],
"raw": {},
"generics": {}
}
]
}
再通过parser将tag转换为真实DOM
<div class="container">
<div class="userinfo">
<span class="user-nickname">{{文字内容}}</span>
</div>
</div>
css也是通过wcsc编译wxss为js,进行相应的转换后,通过 style 标签插入到文档的 head 里面。
总结下:
- 逻辑层调用 setData 方法
- 逻辑层将待传输数据转换成字符串,并拼接到特定的 JS 脚本,通JSBridge过将数据传输到渲染层
- 渲染层接收到后,WebView JS 线程会对脚本进行编译,把待更新数据放入渲染队列,等待 WebView 线程空闲时进行页面渲染
- WebView 线程开始执行渲染时,待更新数据会合并到视图层保留的原始 data 数据,并将新数据套用在 WXML 片段中得到新的虚拟节点树。经过新虚拟节点树与当前节点树的 diff 对比,将差异部分更新到 UI 视图。同时将新的节点树替换旧节点树,用于下一次重渲染
四、生命周期
这里梳理App、组件和页面相应的生命周期流程:
从下方微信官方的原图能够很好的理解小程序页面的生命周期
五、小程序方案与 React Native 对比
微信小程序 | React Native | |
---|---|---|
框架 | * 渲染层使用WebView渲染WXML+WXSS * 逻辑层使用JsCore执行js脚本 * 逻辑层网络请求经由微信native转发 | * JS 层:该层提供了各种供开发者使用的组件以及一些工具库(事件分发等)。 * C++层:主要处理 java/OC 与 JS 的通信(JSBridge)以及执行 JavaScript(JS 脚本引擎)。 * Native 层(Object C/Java 层):主要包括 UI 渲染器、网络通信等工具库。根据不同操作系统有不同的实现。 |
UI渲染 | * 在渲染层,宿主环境会把WXML可以先转成JS对象,然后再渲染出真正的Dom * 在逻辑层发生数据变更的时候,需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染 * 对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面 | 基于react框架(虚拟dom) * 首先Js层通过jsx编写的Virtual Dom来构建Component * Native层将其转成真实DOM插入到原生 App 的页面中 * 当有变更,通过diff算法生成差异对象 * 最终由 Native层将差异对象应用到原生App的页面元素 |
通信 | 视图层<->客户端(大部分原生组件涉及) * iOS 利用WKWebView 的提供 messageHandlers 特性 * 安卓则是往 WebView 的 window 对象注入一个原生方法,最终会封装成 WeiXinJSBridge 这样一个兼容层 逻辑层<->客户端 * iOS平台可以往JavaScripCore框架注入一个全局的原生方法 * 安卓方面则是跟渲染层一致的 | 基于JSCore实现js与java/oc交互 * 把JSX代码解析成javaScript代码 * 读取JS文件,并利用利用JS脚本引擎执行 * 返回一个数组,数组中会描述OC/Java对象,描述对象属性和所需要执行的方法,这样就能让这个对象设置属性,并且调用方法。 |