那有没有办法实现跟 Web Worker 一样的线程安全,同时又兼顾性能保证良好的用户体验呢?这便是微信小程序采用双线程模型的主要目的。
安全高效的双线程模型虽然前面用了 CodePen 这类编程平台做类比,但小程序与 CodePen 的技术需求并不完全相同,主要区别在于小程序并不需要支持所有的 HTML 标签,只提供有限的几类 UI 组件,根据小程序产品定位,我们可以归纳出小程序的主要技术需求可以归纳为下面这样几点。(任何新技术或架构都是为了解决特定的问题,所以有必要了解小程序的主要技术需求。)
限制 UI 组件类型,只允许声明指定的几个组件
小程序在声明组件时并不是使用原生的 HTML 标签,而是只能够通过微信提供的几种内置基础组件,当然你也可以自定义组件,但也是通过对内置基础组件的组合来实现。
保证逻辑线程安全,不允许直接操作 UI 组件
小程序更新 UI 的方式与 Vue/React 等 MVVM 框架类似,JavaScript 代码不能直接操作 DOM(仅做类比,事实上小程序中没有DOM的概念),而是通过更新状态( setState )的方式异步更新 UI ,这个过程中会用到 VDOM 和高效的 diff 算法(这两点并不是我们要讨论的内容,你课下可以自己搜索相关资料)。
能够在线更新,不依赖微信
小程序的宿主是微信,如果使用纯 Native 实现,那么小程序的版本更新必须依赖微信,跟微信的代码一起发版,这样肯定是不行的。如果是纯 Web 实现,安全和性能就很难得到保障。
小程序需要既能够像 Web 一样将资源托管在云端,更新独立;同时又能够保证足够好的安全性和性能。所以最终小程序采用了一种混合的架构模式:使用 Webview 渲染 UI、使用类似Web Worker 的独立线程运行逻辑,这就是双线程模型。
性能需尽量提升,保证用户体验
前面提到的基于 Web Worker 的简易双线程模型性能是很大的问题,小程序的双线程模型并不是使用 Web Worker 子线程,而是一个独立的“主线程”,这样能够保证相对较好的性能。
渲染线程和逻辑线程小程序的双线程指的就是渲染线程和逻辑线程,这两个线程分别承担UI的渲染和执行 JavaScript 代码的工作。如下图所示:
渲染线程使用 Webview 进行 UI 的渲染呈现。Webview 是一个完整的类浏览器运行环境,本身具备运行 JavaScript 的能力,但是小程序并不是将逻辑脚本放到 Webview 中运行,而是将逻辑层独立为一个与 Webview 平行的线程,使用客户端提供的 JavaScript 引擎运行代码,iOS 的JavaScriptCore、安卓是腾讯 X5 内核提供的 JsCore 环境以及 IDE 工具的 nwjs 。
逻辑线程是一个只能够运行 JavaScript 的沙箱环境,不提供 DOM 操作相关的 API,所以不能直接操作 UI,只能够通过 setData 更新数据的方式异步更新 UI。
事件驱动的通信方式注意上图渲染线程和逻辑线程之间的通信方式,与 Vue/React 不同的是,小程序的渲染层与逻辑层之间的通信并不是在两者之间直接传递数据或事件,而是由 Native 作为中间媒介进行转发。
整个过程是典型的事件驱动模式:
渲染层(也可以称为视图层)通过与用户的交互触发特定的事件 event;
然后 event 被传递给逻辑层;
逻辑层继而通过一系列的逻辑处理、数据请求、接口调用等行为将加工好的数据 data 传递给渲染层;
最后渲染层将 data 渲染为可视化的 UI。
这种数据驱动 UI 的模式是现在前端编程领域较为推崇的编程范式,如果你是一个超过 5 年开发经验的前端开发者的话,那么我相信在最初接触到这种模式的时候肯定有一些不适应,因为在此之前 JavaScript 操作 DOM 几乎是一种“业内规则”,甚至有不少针对前端入门的图书、博客和教材都是先从 DOM 操作讲起,现在看来这些确实有些不合时宜了。
而这样逻辑与渲染分离的线程分工模式一方面能够保证运行在逻辑线程沙箱内的 JavaScript 代码是线程安全的,另一方面由于渲染线程的计算量非常小从而保证了对用户交互行为的快速响应,提高了用户体验。
总的来说,跟浏览器的线程模型相比,小程序的双线程模型解决了或者说规避了 Web Worker 堪忧的性能同时又实现了与 Web Worker 相同的线程安全,从性能和安全两个角度实现了提升。可以概括地说,双线程模式是受限于浏览器现有的进程和线程管理模式之下,在小程序这一具体场景之内的一种改进的架构方案。
总结