微信小程序底层框架实现原理
# 微信小程序底层框架实现原理
# 双线程架构
# 是什么?
渲染层与逻辑层分别由两个线程管理,两个线程之间由 Native 层进行统一处理
- 渲染层的界面使用 webview 进行渲染。
- 多个页面对应多个 webview(最多 10 个)
- 逻辑层采用 JSCore 运行 JavaScript 代码
- 只有一个 APP 实例,所有 JS 都在一个线程中执行
# 对比单线程的优势
- 避免单线程中,由于资源加载而阻塞页面解析
- 将微信 SDK、底层基础库等放在 Native 层中,减少应用的包体积和网络请求数量
# 如何解决 H5 的安全问题
- 禁用部分 JS 操作:如获取 DOM、动态执行 JS 代码等
- 为 JS 提供沙箱环境
# 为什么需要提供 WXS
双线程架构中,频繁的跨线程通信(如手势识别等)会带来性能问题。
WXS 可以在渲染层中写部分逻辑,在渲染层单独处理部分频繁改变的数据
# PageFrame
# 解决的问题
每个页面都是一个独立的 webview,其中包含大量内容。小程序如何快速新建并打开页面
# 原理
作为一个模板,其中引用了各种通用资源 js,当多次使用 PageFrame 创建页面时,这些自资源会使用本地缓存。
启动一个页面的步骤:
- 启动一个 webview
- 在其中初始化基础库,以及一些基础库的内部优化
- 注入 WXML 和 WXSS,小程序能在接收到页面初始数据之后马上开始渲染
# Exparser
Exparser 是小程序的组件组织框架,基本参考 Shadow DOM 模型实现。
Shadow DOM 是 WebComponents 中的概念,它允许将隐藏的 DOM 结构附加到常规的 DOM 树中。可以选择外界是否能获取内部结构。
一些 HTML 标签是浏览器内置的 ShadowDOM 组件,如 video、button 等。这些组件不能作为 ShadowDOM 的宿主元素。
Shadow DOM 中的事件,可以选择是否冒泡至宿主元素外部
# WXSS
# 和 css 的区别
- 新增了 rpx 单位,换算方式为屏幕宽度为
375px
时,1px = 2rpx
- 仅支持部分选择器
# 编译方式
WXSS 文件不在 webview 中渲染,而是提前编译为 js。 在该 js 中,会获取设备信息(屏幕宽度等),并以此将 rpx 转换为实际像素。 该 js 内容会通过 eval 进行执行,并最终生成 style 标签插入到页面中。
# WXML 的编译:Virtual DOM
WXML 代码也会编译为 JS,生成一个 $gwx
函数,它用于生成虚拟 DOM。
$gwx
的返回值也是一个函数 generateFun
, generateFun
接受模板渲染所需的动态数据,其返回值是虚拟 DOM 树。
# 事件通讯系统
# 通信机制
- 渲染层和逻辑层的通信都是通过 Native 层转发
- iOS 是利用了 WKWebView 的提供 messageHandlers 特性
- 安卓是往 WebView 的 window 对象注入一个原生方法
- 在微信中封装为
WeiXinJSBridge
作为兼容层 - 在开发者工具中则是用 Websocket 对通信方法进行模拟
# 绑定事件
标签的所有 attribute 都会存在虚拟 DOM 的 attr 属性中,通过正则判断某个属性是否是绑定的事件,找出事件后为节点注册事件。
小程序的事件都是和 HTML 原生事件对应的(如 tap 对应 mouseup),最终会通过 window.addEventListener
添加原生事件的监听
# 触发事件
渲染层中,会拼接事件的 event 参数,创建一个 exparser 事件并触发,通过 sendData 向逻辑层通信
# 数据在两个线程中的传递
页面加载时,data 中的数据会以 JSON 字符串的形式传至渲染层。因此,data 中的数据必须是可以转为 JSON 的类型
字符串,数字,布尔值,对象,数组
# 小程序框架
# 预编译
自己定义一套 DSL(语法规则),一般对应 React 或 Vue,通过源码的 AST 解析还原为微信小程序原生代码的形式
局限性:
- React 或 Vue 出了新的语法,要兼容的话要扩展 DSL
- 兼容问题,小程序不支持的属性,无法编译
# 半编译半运行时:mpvue
基于类 vue 语法
- 将 template 编译为 wxml
- 在 vue 的 patch 流程中,不更新 DOM,而是触发
setData
来更新视图
# 纯运行时框架:remax
# 纯运行时框架要解决的问题
React / Vue 的视图更新都是基于 DOM API 的,但小程序的逻辑层并未提供任何操作节点的 API。
唯一的更新视图的方式,就是 setData
。
# 解决思路:动态 template
WXML 提供模板(template) (opens new window),可以在模板中定义代码片段,然后在不同的地方调用。
使用模板时,通过 is
属性决定要使用的模板是哪一个。而 is
属性的取值可以用双括号语法从而使用 data 中的数据。
因此,实现的思路就是:
- 在 data 中实现一个类似虚拟 DOM 的对象,通过递归的形式在 WXML 中将其渲染为 template
- 更新 DOM 的流程,改为更改这个虚拟 DOM 对象,通过 setData 触发视图更新