深入理解Weex之原理篇

1,031 阅读4分钟

这是我参与 8 月更文挑战的第 22 天,活动详情查看: 8月更文挑战

前言

前端工程师们一直在探索编写一套代码,可以运行在H5、安卓、IOS平台。

在早期,采用Hybrid进行H5混合开发,这种模式APP就相当于一个浏览器,暴露给H5一些native的能力做通信。这种模式开发效率快,但是在性能上有一定的瓶颈。

Weex

到后来,Vue、React这类前端框架开始大行其道,一个重要的概念促使了跨端的发展,那就是虚拟DOM。这是一种DSL的描述,通过不同平台的渲染API实现屏幕绘制。比如在浏览器通过document,在native上通过原生的API进行渲染绘制。

再来后来,Weex诞生了,不得不说,Weex是Vue的一种跨端实现。Weex依旧采取前端H5页面进行开发,同时app在终端的运行体验不输native app。即可以保证快速响应需求,又可以保证用户体验。

工作流程

我们先来看下Weex的工作流程:

  1. 开发者编写Vue文件,通过打包工具打包成多个JS文件;
  2. 在APP端请求JS文件,通过JS执行引擎(JSCore)运行JS文件;
  3. 执行输出VNode,调用WXBridge(Weex渲染引擎)
  4. WXBridge继续调用原生渲染API,完成最终渲染

看完工作流程,我们可以得出以下结论:

  • 把性能损耗较大的渲染工作交给native,而逻辑由JSCore解析执行

构建渲染指令树

在浏览器上它们都使用一致的 DOM API 把 Virtual DOM 转换成真实的 HTMLElement。在 Weex 里的逻辑也是类似的,只是在最后一步生成真实元素的过程中,不使用原生 DOM API,而是使用 JS Framework 里定义的一套 Weex DOM API 将操作转化成渲染指令发给客户端。

JS Framework 提供的 Weex DOM API 和浏览器提供的 DOM API 功能基本一致,在 Vue 和 Rax 内部对这些接口都做了适配,针对 Weex 和浏览器平台调用不同的接口就可以实现跨平台渲染。

JS-Native 通信

在开发页面过程中,除了节点的渲染以外,还有原生模块的调用、事件绑定、回调等功能,这些功能都依赖于 js 和 native 之间的通信来实现。

首先,页面的 js 代码是运行在 js 线程上的,然而原生组件的绘制、事件的捕获都发生在 UI 线程。在这两个线程之间的通信用的是 callNative 和 callJS 这两个底层接口(现在已经扩展到了很多个),它们默认都是异步的,在 JS Framework 和原生渲染器内部都基于这两个方法做了各种封装。

callNative 是由客户端向 JS 执行环境中注入的接口,提供给 JS Framework 调用,界面的节点(上文提到的渲染指令树)、模块调用的方法和参数都是通过这个接口发送给客户端的。为了减少调用接口时的开销,其实现在已经开了更多更直接的通信接口,其中有些接口还支持同步调用(支持返回值),它们在原理上都和 callNative 是一样的。

callJS 是由 JS Framework 实现的,并且也注入到了执行环境中,提供给客户端调用。事件的派发、模块的回调函数都是通过这个接口通知到 JS Framework,然后再将其传递给上层前端框架。

JS Service

Weex 是一个多页面的框架,每个页面的 js bundle 都在一个独立的环境里运行,不同的 Weex 页面对应到浏览器上就相当于不同的“标签页”,普通的 js 库没办法实现在多个页面之间实现状态共享,也很难实现跨页通信。

JS Framework 中实现了 JS Service 的功能,主要就是用来解决跨页面复用和状态共享的问题的,例如 BroadcastChannel 就是基于 JS Service 实现的,它可以在多个 Weex 页面之间通信。

小结

Weex的本质也是一种运行时渲染,而不是编译时。这有点类似于webview容器,但是Weex的最终是由native实现的,所以在运行性能上是可以和原生APP媲美的。