Weex 框架中 JS Framework 的结构

简介: Weex 具有移动端跨平台的特性,JS Framework 是其中比较关键的一层。首先来看一下 JS Framework 在 Weex 中的位置: ![Weex 整体结构](http://gtms02.alicdn.com/tps/i2/TB1ootBMpXXXXXrXXXXwi60UVXX-596-397.png) 从图中可以看出 Weex 整体的工作流程。首先开发者可以声明式的定义组件

Weex 具有移动端跨平台的特性,JS Framework 是其中比较关键的一层。首先来看一下 JS Framework 在 Weex 中的位置:
Weex 整体结构

从图中可以看出 Weex 整体的工作流程。首先开发者可以声明式的定义组件,形成 .we 文件,通过 weex-toolkit 提供的工具将 .we 文件转为 JS Bundle。JS Framework 接收并执行 JS Bundle 的代码,并且执行数据绑定、模板编译等操作,然后输出 json 格式的 Virtual DOM 传递给移动端,同时也提供了 callNativecallJS 接口,方便 JS Framework 和 Native 的通信。同样的一份 json 数据,在不同平台的渲染引擎中能够渲染成不同版本的 UI,这也是 Weex 可以实现动态化的原因。

简而言之,JS Framework 的输入是 JS Bundle,输出是 json 格式的 Virtual DOM,同时也提供了与 Native 通信的方法。

代码结构

文中代码的版本是 v0.15 。

weex/html5/default
    ├── api           // 定义 Vm 上的接口
    │   ├── methods.js
    │   └── modules.js
    ├── app           // 页面实例相关代码
    │   ├── bundle.js
    │   ├── ctrl.js
    │   ├── differ.js
    │   ├── downgrade.js
    │   ├── index.js
    │   └── register.js
    ├── core          // 数据监听相关代码
    │   ├── LICENSE
    │   ├── array.js
    │   ├── dep.js
    │   ├── object.js
    │   ├── observer.js
    │   ├── state.js
    │   └── watcher.js
    ├── util          // 工具函数
    │   ├── LICENSE
    │   └── index.js
    ├── vm            // 组件模型相关代码
    │   ├── compiler.js
    │   ├── directive.js
    │   ├── dom-helper.js
    │   ├── events.js
    │   └── index.js
    ├── config.js
    └── index.js      // 入口文件

初始化

出于性能考虑,JS Framework 自身只会在应用启动时初始化一次,多个页面共享一份 Weex 实例和方法,包括与 Native 的通信。虽然 Weex 只有一份,但是每个 JS Bundle 是会创建不同的 App 实例的,每个实例都有唯一 id,在与 Native 通信时也要传递 id 参数。具体细节可以参考:《Weex 在 JS Runtime 内的多实例管理》

初始化 JS Framework

Weex 实例包含了如下方法:

Weex API

注:在 web 环境下,挂在 window 上的变量名是小写 weex,而且经过了封装,并非 JS Framework 直接暴露的接口。

创建 App 实例

在获取到 JS Bundle 后,会调用 createInstance 创建页面实例。它首先会 new App() 创建新的 App 实例对象,并且把对象放入 instanceMap 中。app 实例中有如下几个常用属性:

  • id 与 Native 端通信时的唯一标识。
  • vm View Model,组件模型,包含了数据绑定相关功能。
  • doc Virtual DOM 中的根节点。

由于 JS Bundle 是工具打包生成的 js 代码,app 实例创建完成后,会通过 new Function 的方式来执行。在代码中用到的 definerequirebootstrapdocumentregisterrender 等方法都是在 JS Framework 的 init 中定义的,以参数的方式传递到 JS Bundle 中。new Function 中代码将会在全局环境中执行,并不能获取到 JS Framework 执行环境中的数据(除了以参数传递过去的那些)。JS Bundle 本身也用了立即执行函数做封装,并不会污染全局环境。

注: 使用 new Function 可能会导致一些性能问题,目前正在尝试其他执行方式,新版本创建 App 实例的过程可能会有所不同。

执行 JS Bundle 中的代码

在加载 JS Bundle 过程中,会首先执行 define & require 的功能,用户自定义的模块,放在了 app.customComponentMap 中,然后对调用 bootstrap 方法启动根组件。bootstrap 方法首先会校验一下参数和环境,如果不符合条件可能会触发页面降级(也可以手动设置使页面降级,这一特性可以在 Native 出现问题时,使页面降级为 html5 运行)。

bootstrap 最后会创建应用的 Vm 实例,整个过程可以分成三个步骤:

  1. initEvents 初始化事件和生命周期。
  2. initState 实现数据绑定功能。
  3. 编译模板并且绘制 Native UI。

初始化事件和生命周期

initEvents 会依次绑定三类事件:options 参数中定义的事件、externalEvents 外部事件、内置的生命周期事件,前两项通常都为 null,生命周期包含了initcreatedready 三个钩子。生命周期函数可以在组件中定义,具体触发时机如下:

module.exports = {
    data: {},
    methods: {},

    init: function () {
        console.log('在初始化内部变量,并且添加了事件功能后被触发');
    },
    created: function () {
        console.log('完成数据绑定之后,模板编译之前被触发');
    },
    ready: function () {
        console.log('模板已经编译并且生成了 Virtual DOM 之后被触发');
    }
}

事件绑定完毕后会立即触发 hook:init 事件,并且将 _inited 属性设置为 true。

实现数据绑定功能

数据绑定的核心思想是基于 ES5 的 Object.defineProperty 方法,在 vm 实例上创建了一系列的 getter / setter,支持数组和深层对象,在设置属性值的时候,会派发更新事件。这部分功能的实现借鉴了 vue 的思路以及部分代码。数据绑定的过程主要涉及了三个对象:

Data_Binding

在执行数据绑定之前,会将参数中传递的数据 merge 到 _data 属性中来,然后执行 initState,分为三个步骤:

  1. initData,设置 proxy,监听 _data 中的属性;然后添加 reactiveGetter & reactiveSetter 实现数据监听。 (这个过程比较繁琐,涉及很多技巧,以后新开文章讲解)
  2. initComputed,初始化计算属性,只有 getter,在 _data 中没有对应的值。
  3. initMethods 将 _method 中的方法挂在实例上。

创建的 Observer 的实例会挂载到 _data.__ob__ 属性中。数据绑定结束后会触发 hook:created 事件,并且将 _created 属性设置为 true。

编译模板

模板编译函数 build 会调用 compile 函数,compile 会递归编译整个模板,这个过程会展开自定义的组件,编译指令,也会执行一些数据绑定,最终生成 Virtual DOM。其中,真正创建节点的是 createBodycreateElement 两个方法,createBody 只会在创建根节点时调用。

此外还有一个比较常用的方法:createBlock,它会创建一个特殊格式的 Block,在真实 Element 的开始和结束位置会添加两个 Comment 节点,在编译过程中可以和 Element 同等对待。之所以这么设计,是为了方便编译 ifrepeat 等指令,当其绑定的数据项发生变化时,可以快速定位到需要改变的 DOM 节点,仅在 start 和 end 两个 Comment 元素之间执行操作。

在编译过程中,会根据节点的类型不同,将编译逻辑分派到不同的函数中,主要包含以下几种:

  • compileRepeat: 编译 repeat 指令,同时会执行数据绑定,在数据变动时会触发 DOM 节点的更新。
  • compileShown: 编译 if 指令,也会执行数据绑定。
  • compileFragment: 编译多个节点,创建 Fragment 片段。
  • compileChildren: 编译子组件,用于实现递归。
  • compileType: 编译动态类型的组件。
  • compileCustomComponent: 编译展开用户自定义的组件,这个过程会递归创建子 vm,并且绑定父子关系,也会触发子组件的生命周期函数。
  • compileNativeComponent: 编译内置原生组件。这个方法会调用 createBodycreateElement 与原生模块通信并创建 Native UI。

绘制 Native UI

在 JS Framework 中实现的 Virtual DOM,包含了四类对象:DocumentNodeElementComment,接口的定义也基本上都和 W3C 标准保持一致,不过要更为精简一些。

Virtual_DOM

不过,这里创建的是 Virtual DOM,如何在不同的平台上创建 Native UI ?

Document 对象中包含一个 listener 属性,它可以向 Native 端发送消息,每当创建元素或者是有更新操作时,listener 就会拼装出制定格式的 action,并且最终调用 callNative 把 action 传递给原生模块,原生模块中也定义了相应的方法来执行 action 。

例如当某个元素执行了 element.appendChild() 时,就会调用 listener.addElement(),然后就会拼成一个如下格式的 action 通过 callNative 传递给原生模块。

{
    module: 'dom',
    method: 'addElement',
    args: [] // 传递给原生模块的参数
}

模板编译的过程需要递归生成整个 Virtual DOM tree,期间还会与原生模块密集通信,会消耗很多内存和计算资源,这个过程通常也是性能瓶颈。

在模板编译完成后,会触发 hook:ready 事件。

结语

这篇文章简单讲述了 JS Framework 的功能以及实现方法,是我自己对 JS Framework 的理解,如果发现了不严谨地方或者有其他观点,欢迎一起探讨。

目录
相关文章
|
13天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端框架
【10月更文挑战第34天】在数字化时代,后端开发如同一座桥梁,连接着用户界面与数据处理的两端。本文将通过Node.js这一轻量级、高效的平台,带领读者领略后端框架的魅力。我们将从基础概念出发,逐步深入到实战应用,最后探讨如何通过代码示例来巩固学习成果,使读者能够在理论与实践之间架起自己的桥梁。
|
1月前
|
JavaScript 前端开发 中间件
探索后端技术:Node.js与Express框架的完美融合
【10月更文挑战第7天】 在当今数字化时代,Web应用已成为日常生活不可或缺的一部分。本文将深入探讨后端技术的两大重要角色——Node.js和Express框架,分析它们如何通过其独特的特性和优势,为现代Web开发提供强大支持。我们将从Node.js的非阻塞I/O和事件驱动机制,到Express框架的简洁路由和中间件特性,全面解析它们的工作原理及应用场景。此外,本文还将分享一些实际开发中的小技巧,帮助你更有效地利用这些技术构建高效、可扩展的Web应用。无论你是刚入门的新手,还是经验丰富的开发者,相信这篇文章都能为你带来新的启发和思考。
|
1天前
|
缓存 监控 JavaScript
Vue.js 框架下的性能优化策略与实践
Vue.js 框架下的性能优化策略与实践
|
1月前
|
JavaScript 前端开发 API
Vue.js:现代前端开发的强大框架
【10月更文挑战第11天】Vue.js:现代前端开发的强大框架
65 41
|
19天前
|
JavaScript 中间件 API
Node.js进阶:Koa框架下的RESTful API设计与实现
【10月更文挑战第28天】本文介绍了如何在Koa框架下设计与实现RESTful API。首先概述了Koa框架的特点,接着讲解了RESTful API的设计原则,包括无状态和统一接口。最后,通过一个简单的博客系统示例,详细展示了如何使用Koa和koa-router实现常见的CRUD操作,包括获取、创建、更新和删除文章。
36 4
|
26天前
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的完美结合
【10月更文挑战第21天】本文将引导你走进Node.js和Express框架的世界,探索它们如何共同打造一个高效、可扩展的后端服务。通过深入浅出的解释和实际代码示例,我们将一起理解这一组合的魅力所在,并学习如何利用它们来构建现代Web应用。
46 1
|
14天前
|
Web App开发 JavaScript 前端开发
构建高效后端服务:Node.js与Express框架的实践
【10月更文挑战第33天】在数字化时代的浪潮中,后端服务的效率和可靠性成为企业竞争的关键。本文将深入探讨如何利用Node.js和Express框架构建高效且易于维护的后端服务。通过实践案例和代码示例,我们将揭示这一组合如何简化开发流程、优化性能,并提升用户体验。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
16天前
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的融合之道
【10月更文挑战第31天】在追求快速、灵活和高效的后端开发领域,Node.js与Express框架的结合如同咖啡遇见了奶油——完美融合。本文将带你探索这一组合如何让后端服务搭建变得既轻松又充满乐趣,同时确保你的应用能够以光速运行。
24 0
|
1月前
|
JSON JavaScript 前端开发
Node.js Express 框架
10月更文挑战第7天
30 2
|
1月前
|
JavaScript 前端开发 API
下一篇
无影云桌面