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与Express框架的实战指南
【9月更文挑战第6天】在数字化时代的潮流中,后端开发作为支撑现代Web和移动应用的核心,其重要性不言而喻。本文将深入浅出地介绍如何使用Node.js及其流行的框架Express来搭建一个高效、可扩展的后端服务。通过具体的代码示例和实践技巧,我们将探索如何利用这两个强大的工具提升开发效率和应用性能。无论你是后端开发的新手还是希望提高现有项目质量的老手,这篇文章都将为你提供有价值的见解和指导。
|
23天前
|
JavaScript 前端开发 中间件
构建高效后端服务:Node.js与Express框架的完美搭档
【8月更文挑战第28天】在追求高性能、可扩展和易维护的后端开发领域,Node.js和Express框架的组合提供了一种轻量级且灵活的解决方案。本文将深入探讨如何利用这一组合打造高效的后端服务,并通过实际代码示例展示其实现过程。
|
24天前
|
JavaScript 前端开发 开发者
Vue.js 框架大揭秘:响应式系统、组件化与路由管理,震撼你的前端世界!
【8月更文挑战第27天】Vue.js是一款备受欢迎的前端JavaScript框架,以简洁、灵活和高效著称。本文将从三个方面深入探讨Vue.js:响应式系统、组件化及路由管理。响应式系统为Vue.js的核心特性,能自动追踪数据变动并更新视图。例如,通过简单示例代码展示其响应式特性:`{{ message }}`,当`message`值改变,页面随之自动更新。此外,Vue.js支持组件化设计,允许将复杂界面拆分为独立且可复用的组件,提高代码可维护性和扩展性。如创建一个包含标题与内容的简单组件,并在其他页面中重复利用。
47 3
|
24天前
|
JavaScript 中间件 API
深入浅出Node.js后端框架——Express
【8月更文挑战第27天】在这篇文章中,我们将一起探索Node.js的热门框架Express。Express以其简洁、高效的特点,成为了许多Node.js开发者的首选框架。本文将通过实例引导你了解Express的核心概念和使用方法,让你快速上手构建自己的Web应用。
|
21天前
|
存储 JavaScript NoSQL
构建高效Web应用:使用Node.js和Express框架
【8月更文挑战第30天】本文将引导你了解如何使用Node.js和Express框架快速搭建一个高效的Web应用。通过实际的代码示例,我们将展示如何创建一个简单的API服务,并讨论如何利用中间件来增强应用功能。无论你是新手还是有经验的开发者,这篇文章都将为你提供有价值的见解。
|
1天前
|
缓存 监控 JavaScript
构建高效后端服务:Node.js与Express框架的完美结合
【9月更文挑战第18天】在数字化时代的浪潮中,后端服务的效率和稳定性成为了企业竞争力的关键。本文将深入探讨如何使用Node.js和Express框架来构建一个既高效又稳定的后端服务,同时通过实际代码示例,展示如何优化性能并确保服务的高可用性。
|
8天前
|
Web App开发 缓存 JavaScript
构建高效后端服务:Node.js与Express框架的完美结合
【9月更文挑战第11天】本文将引导开发者探索如何利用Node.js和Express框架搭建一个高效的后端服务。文章不仅深入讲解了这两个工具的核心概念,还通过实际示例展示了它们的强大功能和易用性。读者将学会如何处理HTTP请求、设计RESTful API以及优化应用性能。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的知识,帮助你在后端开发领域更进一步。
|
24天前
|
Web App开发 JavaScript 中间件
构建高效后端服务:Node.js与Express框架的深度整合
【8月更文挑战第27天】 在现代Web开发中,后端服务的高效性至关重要。本文深入探讨了如何利用Node.js的非阻塞I/O特性和Express框架的简洁性来打造高性能的后端服务。我们将通过具体案例,展示如何在不牺牲代码可读性和可维护性的前提下,实现高效的请求处理和服务端逻辑。文章旨在为开发者提供一个清晰的指导,帮助他们在构建后端服务时做出更明智的技术选择。
|
22天前
|
JSON JavaScript 中间件
深入浅出Node.js后端开发之Express框架应用
【8月更文挑战第29天】本文将带领读者快速了解并掌握使用Express框架进行Node.js后端开发的基础和进阶知识。我们将一起探索Express的安装、基本使用方法,并通过实际代码示例学习如何搭建一个简单的Web服务器。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
24天前
|
JavaScript PHP 开发者
PHP中的异常处理与自定义错误处理器构建高效Web应用:Node.js与Express框架实战指南
【8月更文挑战第27天】在PHP编程世界中,异常处理和错误管理是代码健壮性的关键。本文将深入探讨PHP的异常处理机制,并指导你如何创建自定义错误处理器,以便优雅地管理运行时错误。我们将一起学习如何使用try-catch块捕获异常,以及如何通过set_error_handler函数定制错误响应。准备好让你的代码变得更加可靠,同时提供更友好的错误信息给最终用户。