小程序
核心-双线程架构

从上图可以看到,小程序分为了 渲染层(Webview)和逻辑层(App service),然后中间是系统层(Native)通过 JsBridge连接,那么为什么要分成两块呢?
- 提高用户体验(ui 和逻辑分离,避免页面长时间阻塞和卡顿)
- 优化应用性能(运行在不同的线程中,可以同时渲染或者计算)
- 开发效率更高(解耦和松散耦合)
浏览器本省是一个单线程架构,主要原因是 js 允许操作 Dom,所以 js 线程和渲染线程只能互斥运行 那么问题又来了,怎么做到的呢?
- 根 本原因就是微信小程序禁止 js 操作 DOM。
WXML
说到小程序,那必不可少的就是 WXML 和 WXSS,先说下 WXML
WXML 全称是 WeiXin Markup Language,是小程序框架设计的一套标签语言,结合小程序的基础组件、事件系统,可以构建出页面的结构。
其实根本还是类似 Vue 的虚拟 DOM 结构,小程序会将 WXML 解析为 虚拟 DOM 的对象结构,包括了 tag, children, attr等,然后再通过虚拟 DOM 编译为 js 文件,最后再插入到渲染层的 script 标签中。
WXSS
WXSS 是小程序中使用的样式语言,WXSS 具有 CSS 的大部分特性,同时它对 CSS 进行了扩充以及修改。 小程序中使用的尺寸单位为 rpx(Responsive px),不同于 h5 中对于 px 的处理,需要使用 postcss 进行统一的转换,小程序底层已经为开发者做好了这层转换,那具体它是怎么做到的呢?看下图

WXSS 同样会经过编译,最终的编译产物为 wxss.js,不同于 WXML 通过 script 标签的形式插入到渲染层,wxss.js 则是通过 eval 的方式注入到渲染层代码中。
渲染层
渲染层用来渲染页面结构,主要由 WebView 进行渲染,一个小程序可以存在多个界面,所以渲染层可能存在多个 WebView 线程。
渲染线程中存在着以下全局变量:
- webviewId:webview 的唯一标识,当用户打开一个小程序页面的时候,相当于打开了一个 webview,不同的 webview 用 webviewid 来区分;
- wxAppCode:整个页面的 json wxss wxml 编译之后都存储在这里;
- Vd_version_info:版本信息;
- ./dev/wxconfig.js:小程序默认总配置项,包括用户自定义与系统默认的整合结果。在控制台输入__wxConfig 可以看出打印结果;
- ./dev/devtoolsconfig.js:小程序开发者配置,包括 navigationBarHeight,标题栏的高度,状态栏高度,等等,控制台输入__devtoolsConfig 可以看到其对应的信息;
- ./dev/deviceinfo.js:设备信息,包含尺寸/像素点 pixelRatio;
- ./dev/jsdebug.js:debug 工具;
- ./dev/WAWebview.js:渲染层底层基础库;
- ./dev/hls.js:优秀的视频流处理工具;
- ./dev/WARemoteDebug.js:底层基础库调试工具;
逻辑层
逻辑层采用 JSCore 线程运行 JS 脚本。逻辑层主要用来逻辑处理、数据请求、接口调用等。
在小程序中,逻辑层只有一个,但是渲染层有多个,渲染层和逻辑层之间是通过微信客户端进行桥接通信的。那具体是怎么实现的呢?
其实它使用的就是 WeixinJSBridge 通信机制。主要方式有:
- invoke:调用 native API;
- invokeCallbackHandler:Native 传递 invoke 方法回调结果;
- publish:渲染层用来向逻辑业务层发送消息,也就是说要调用逻辑层的事件方法;
- subscribe:订阅逻辑层消息;
- subscribeHandler:视图层和逻辑层消息订阅转发;
- setCustomPublishHandler:自定义消息转发;
那么渲染层如何向逻辑层通信?这里就是小程序的事件系统流程了。

开发者在 DOM 上通过@click 绑定事件,WXML 文件被编译的时候,会通过$gwx 函数生成虚拟 DOM,然后小程序执行的时候渲染层底层基础库会对虚拟 DOM 进行解析,事件绑定最终会以 attr 属性的形式生成到虚拟 DOM 中,所以底层基础库通过 applyPropeties 解析事件并通过 addEventListener 绑定到相应 DOM 并声明回调。
用户点击相应 DOM 时,Exparser 组件系统接收到这个事件,然后开始执行回调。回调函数在逻辑层,事件的触发在渲染层,此时,小程序会通过 setData 发送数据到逻辑层,这个时候 WeixinJSBridge 就派上用场了,渲染层调用 publish 方法发送数据,逻辑层通过 registercallback 进行监听,并执行相应的回调。此时,渲染层到逻辑层的通信流程结束。
那逻辑层又是如何将改变后的数据回传给渲染层的呢?逻辑层改变数据之后,同样是触发 setData 方法,然后渲染层通过 subscribe 进行监听,从 eventname 和触发事件时候记录的回调函数来判断是哪个事件被触发了,从而获取动态数据。