天天看点

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

前段时间进行了weex页面尝试, 页面滚动加载渲染得非常流畅, 让h5页面拥有了native般的体验。

weex代码结构如下,重点关注其js-framework实现。

阅读js-framework代码,我整理了一份思维导图。

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

framework.js是instance创建的入口,可以从这个文件开始自顶向下地阅读代码,了解其工作原理。可以重点理解它的dom结构,初始化过程,数据更新过程,下面我也将从这几个方面进行描述。

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

weex的dom结构由<code>document</code>、<code>element</code>、<code>comment</code>三类组成。element创建普通节点,comment用于创建frag block节点。每个节点都有一个唯一的<code>ref</code>值,可以很方便地在文档中被查询到,同时记录其父节点<code>parentref</code>,通过这种’双向链表‘的操作可以方便进行节点拼接和获取。文档树节点document记录整个dom的结构,同时在document上绑定eventmanager事件处理器和listener监听操作处理器。<code>eventmanager</code>记录每个绑定了事件的节点和它对应的事件处理函数,提供事件的添加、删除和触发。<code>listener</code>提供了dom操作转化为callnative的能力,通过将每一个操作转化为对应类型的actions,如<code>createbody</code>、<code>addelement</code>,并将每一个actions记录updates数组。

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

weex复用了 vue.js 的数据监听和依赖收集的代码实现。通过observer、directive、watcher之间的协作,建立数据(model)和视图(view)的关联关系:

observer 对 data 进行了监听,并且提供订阅某个数据项的变化的能力

compiler解析template,并解析其中的 directive,得到每一个 directive 所依赖的数据项及其更新方法

watcher 把上述两部分结合起来,即把 directive 中的数据依赖订阅在对应数据的 observer 上,这样当数据变化的时候,就会触发 observer,进而触发相关依赖对应的视图更新方法。

当我们在浏览器中输入我们的bundle地址,其解析渲染为html过程大致可以分解为createinstance-&gt;initinstace-&gt;run bundle-&gt;define-&gt;boostrap-&gt;create vm-&gt;生命周期函数。可细化为下面这些步骤:

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

<code>initinstance</code>: 根据webpack打包后的js代码来定义实例。

<code>define</code>: 解析代码中的<code>__weex_define__("@weex-component/bottom-bar")</code>定义的component,包含依赖的子组件。并将component记录到<code>customcomponentmap[name] = exports</code>数组中,维护组件与组件代码的对应关系。由于会依赖子组件,因此会被多次调用。

define执行后的appinstance实例结构:

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

<code>bootstrap</code>:解析代码中的<code>__weex_bootstrap__("@weex-component/30d1c553f95b5f87afb6a1cff70a7bbd")</code>执行当前页面,提取customcomponentmap中记录的组件代码进行初始化。只会执行一次。

<code>downgrade</code>: 检测页面降级配置进行页面降级。

<code>initevents</code>: 绑定events和lifecycle(init、create、ready)执行的钩子。

<code>initscope</code>: 执行initdata()、initcomputed、initmethods。初始化data、computed属性和methods,并进行data的observer监听。

<code>build</code>: 根据预留选项<code>opt.replace</code>进行编译,目前该选项还未被实质使用。编译完成后执行ready的钩子命令,执行ready。

<code>compile</code>: 编译视图。

<code>updateactions</code>: 检测是否有数据更新需要执行。

<code>createfinish</code>: 表明dom结构创建完成,想callqueue队列中添加一个'createfinish'的actions。

<code>processcallqueue</code>: 依次执行队列中的actions,进行节点渲染到页面的过程,为了性能考虑,通过requestanimationframe进行分帧渲染。

callqueue队列

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

通过初始化过程我们可以得到<code>init -&gt; 数据监听 -&gt; created -&gt; 视图生成 -&gt; ready</code>,为了避免重复的视图操作,可在init进行数据的获取,created阶段进行数据的赋值和修改。

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

从上图可知,weex提供了两种append方式:tree、node。

tree:先渲染子节点树,最后渲染父节点

node:先渲染父节点,然后子节点一个个append

进行了不同的节点数量,vm创建耗时采样对比,从图中可以看出当节点个数较多的时候tree模式比node模式渲染的快

| 1个文本节点 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 |

| ----- | ------ | ------- | ------- | ----- | ------ | ------- |

| tree | 8ms | 10ms| 12ms| 9ms| 10ms| 9.8ms|

| node | 10ms| 9ms| 9ms| 9ms| 9ms| 9.2ms|

| 20个文本节点 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 |

| tree | 17ms | 18ms| 18ms| 16ms| 18ms| 17.4ms|

| node | 18ms| 17ms| 21ms| 17ms| 18ms| 18.2ms|

| 50个文本节点 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 |

| tree | 32ms | 28ms| 26ms| 27ms| 27ms|28ms|

| node | 30ms| 29ms| 34ms| 33ms| 31ms| 31.4ms|

| 100个文本节点 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 |

| tree | 44ms | 41ms| 37ms| 37ms| 44ms|40.6ms|

| node | 46ms| 44ms| 41ms| 44ms| 43ms| 43.6ms|

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

<code>attachtarget</code>: 进行节点渲染的时候,将每个append动作细化为具体的actions,置入callqueue队列中。

<code>updateactions</code>: 检测是否有diff,如果有,则执行diff中记录的task

<code>calltasks</code>: 调用callnative,根据执行状态判断是否执行callqueue列表中的人物或者置入callqueue队列中。

执行click事件,其中修改了data数据值,执行顺序如下:

浅析weex之vdom渲染前言一. 文件结构二. 主要类分析三. 初始化过程四. 数据更新过程总结

calljs响应事件、接受事件,通过eventmanager获得事件目标响应函数并fire执行,通过watcher监听数据修改,如果数据前后不等则将修改更新操作记入diff中,同时通知订阅它的依赖继续收集更新操作。最终执行updateactions完成数据更新操作。

通过上文分析,可以认为:

tree模式比node模式渲染的快。

每个节点的创建都对应一个callqueue任务,节点逐个逐个的append到页面中。

依赖发布订阅模式收集依赖,监听每一个属性的变化,可直接获取更新操作,映射到dom结构中。

与native交互通过 callnative()方法;响应js调用采用calljs()方法。

以上是个人拙见,本文中描述不正确的地方欢迎指正~

继续阅读