跳到主要内容

小程序

核心-双线程架构

从上图可以看到,小程序分为了 渲染层(Webview)和逻辑层(App service),然后中间是系统层(Native)通过 JsBridge连接,那么为什么要分成两块呢?

  • 提高用户体验(ui 和逻辑分离,避免页面长时间阻塞和卡顿)
  • 优化应用性能(运行在不同的线程中,可以同时渲染或者计算)
  • 开发效率更高(解耦和松散耦合)

浏览器本省是一个单线程架构,主要原因是 js 允许操作 Dom,所以 js 线程和渲染线程只能互斥运行 那么问题又来了,怎么做到的呢?

  • 根本原因就是微信小程序禁止 js 操作 DOM。

WXML

说到小程序,那必不可少的就是 WXMLWXSS,先说下 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 和触发事件时候记录的回调函数来判断是哪个事件被触发了,从而获取动态数据。

运行机制

小程序启动运行两种情况:

  • 冷启动: 用户首次打开或者小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动,即为冷启动。

  • 热启动: 用户已经打开过小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需要将后台态的小程序切换到前台,这个过程就是热启动。

信息

小程序每个视图层页面内容都是通过 pageframe.html 模板来生成的,包括小程序启动的首页。

  • 首页启动时,即第一次通过 pageframe.html 生成内容后,后台服务会缓存 pageframe.html 模板首次生成的 html 内容;
  • 非首次新打开页面时,页面请求的 pageframe.html 内容直接走后台缓存;
  • 非首次新打开页面时,pageframe.html 页面引入的外链 js 资源走本地缓存; 这样在后续新打开页面时,都会走缓存的 pageframe 的内容,避免重复生成,快速打开一个新页面。

通知更新

主要是在开发人员提交审核通过发布小程序之后,提醒用户重启更新小程序 直接上代码

const updateManager = wx.getUpdateManager();

updateManager.onCheckForUpdate(function (res) {
// 请求完新版本信息的回调
console.log(res.hasUpdate);
});

updateManager.onUpdateReady(function () {
wx.showModal({
title: "更新提示",
content: "新版本已经准备好,是否重启应用?",
success(res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
},
});
});

updateManager.onUpdateFailed(function () {
// 新版本下载失败
});