详解 Weex JS Framework 的编译过程
之前写了一篇文章《Weex 框架中 JS Framework 的结构》概述了 JS Framework 的整体结构,其中编译过程写的有些简略,这里再详细介绍一下。
一句话概括 JS Framework 的编译过程就是: 将 JS Bundle 转换成 Virtual DOM 发送到原生模块渲染。
这个过程涉及三种数据类型:JS Bundle
、Virtual DOM
、Vm
。
JS Bundle
是由.we
文件转换过来的,会被视为代码而执行。Virtual DOM
是描述页面结构的 JSON 数据,用于给原生模块发送消息。Vm
是 View Model 的简写,属于 MVVM 结构中的一部分,会执行模板编译、数据绑定等操作。
简化版的 Vm 构造函数如下:
// html5/default/vm/index.js
function Vm() {// ...// 初始化生命周期initEvents(this, externalEvents)this.$emit('hook:init')// ...// 监听 data 中的数据initState(this)this.$emit('hook:created')// ...// 启动模板编译build(this)
}
在 Vm 构造函数最后调用 build
函数启动模板的编译,是一种尾递归,便于 js 引擎优化。
整体编译流程
在调用 build
触发编译后,真正实现编译功能的是 compile
方法(代码位置在 html5/default/vm/compiler.js
中)。build
方法并不递归,它做的只是根据配置项选择合适的参数,然后调用 compile
方法。
compile
方法
compile
接受四个参数:
vm
: 待编译的 Vm 对象。target
: 需要编译的节点,是模板中的标签经过 transformer 转换后的结构。dest
: 当前节点父节点的 Virtual DOM。meta
: 元数据,在内部调用时可以用来传递数据。
在 compile 函数的最后,会调用 compileNativeComponent
编译生成原生组件,除了它和 createBlock
两个方法以外,其他都是会触发递归的。
分发编译逻辑
compile
函数内部并没有渲染逻辑,他只是将不同类型的节点交给不同的函数来编译;换句话说说,它是负责逻辑的分发和实现递归的。同样职责的函数还有 compileChildren
,他会对每个子节点(模板中的子标签)调用 compile
。 compileFragment
方法可以编译数组,其中每个数据项都会调用 compile
方法,但是会共用一个 Block。
创建 Block
在上述的方法中,createBlock
是实际的创建节点的操作,可以视为递归的终止条件。创建的 block 是封装后的 Virtual DOM 节点,结构如下:
{blockId,start, // 节点的起点位置,Comment 节点,在当前元素的前一个位置element, // 实际的节点元素,Element 节点end, // 节点的结束位置,Comment 节点,在当前元素的后一个位置
}
这样做的目的一方面是将 Element 和 Fragment 操作统一化,另一方面是为了 UI 更新时能够快速定位到节点。
编译指令
Weex 的模板标签上支持使用指令,最基本的是 if
和 repeat
指令,详细用法参考官方文档。
if
指令
if
指令可以控制节点的显示和隐藏,用法如下:
<text if={{visible}}>Show something here.</text>
这段模板会被 transformer 转换成下面这种结构:
{"type": "text","shown": function () { return this.visible }
}
编译 if
指令时,先会创建一个 Block,然后创建 Watcher
执行数据绑定。当 this.visible
发生变化时,会更新视图,若 shown
函数返回为真,则会调用 compile
方法重新编译节点,否则会调用 removeTarget
将节点从父节点中删除。
repeat
指令
repeat
指令可以根据模板循环渲染数组中的所有数据。假设有如下某个 .we
文件:
<script>module.exports = {data: {images: [{ source: 'somewhere/a.png' },{ source: 'somewhere/b.png' },{ source: 'somewhere/c.png' }]}}
</script>
<template><list><cell repeat={{images}}><image src="{{source}}"></image></cell></list>
</template>
编译生成的结果如下:
{"type": "list","children": [{"type": "cell","append": "tree","repeat": function () { return this.images },"children": [{"type": "image","attr": {"src": function () { return this.source }}}]}]
}
遇到 repeat
指令时,会调用 compileRepeat
方法,它先整理好数据,然后调用 bindRepeat
执行编译。编译过程中会将 repeat
指令所在的节点视为模板,循环展开对应的数据,逐条调用 compileItem
渲染每个节点。期间也会添加数据绑定,当数组中的数据有变化时,会自动更新 List。
append
属性
如果仔细看一下上面 repeat
指令转换出来的代码,会发现 cell 节点上有一个 append
属性,这个属性在官方文档中写的比较详细了,它是用来控制渲染次序的,属于比较底层的属性,在内部指令中用到了,开发者通常不会用到。这里再总结一下:
append="tree"
会先编译子节点,再编译自身。编译速度快,但是容易造成较长时间的白屏。append="node"
会先编译自身,再编译子节点。整体编译速度略慢,但是用户体验好一些。- 默认的编译方式是
node
,先创建容器,再创建内容。
不过 repeat
指令默认的编译方式是 tree
;由于内容可变,它是先编译生成所有子节点,然后再编译自身,避免频繁地插入操作,这种编译方式也比较符合列表的特性。
编译组件
除了内置标签以外,Weex 还支持自定义的组件(标签),这是最基本也是最好用特性了。
编译自定义的组件
每个组件(.we 文件)都对应的是一个 Vm
实例,当编译过程中遇到某标签不是内置标签而是一个自定义的组件时,会创建一个新的 Vm
实例,合并父子组件中添加的样式,同时也添加了生命周期的钩子:
init
: 设置子组件 id。created
:bindSubVm
合并父子组件中定义的属性。ready
: 调用compileChildren
开始编译子组件中的节点。
值得注意的是,创建新的 Vm
,又会执行本文从开头开始讲的所有步骤:初始化数据、数据绑定、递归编译各种节点…… 所以说整个编译过程包含了大量的递归,函数调用栈比较深,会消耗大量的内存和时间。
编译生成原生组件
在 compile
函数结尾会调用 compileNativeComponent
绘制原生组件,也就是说,原生组件的绘制是在递归编译过程中进行的,不需要等待完整的 Virtual DOM tree 拼好之后再绘制原生 UI,页面可以实现流式渲染,持续不断的渲染碎片化的 Virtual DOM。
compile
的各种流程产出的是 Virtal DOM,而 compileNativeComponent
实现的是将碎片化的 Virtual DOM 通过 callNative
发送渲染指令给原生模块,通知其绘制 Native UI。原生模块在绘制 UI 的过程中,如果发生了错误,会返回 -1
存放在 app.lastSignal
中,此时 JS Framework 会终止编译。
结语
接前一篇讲 JS Framework 的文章,这篇文章详细介绍了组件的编译流程,涉及很多技术细节,这些细节有可能在以后的版本中有改动。了解 JS Framework 的实现细节,有助于开发者在使用时能避开不恰当的用法,少踩一些坑,希望这篇文章能对大家有所帮助,如果有疑问或者有不同看法,欢迎来找我探讨。
详解 Weex JS Framework 的编译过程相关推荐
- php滚动窗口多条动态,详解原生JS是实现控制多个滚动条同步跟随滚动
本文主要和大家详解详解原生JS是实现控制多个滚动条同步跟随滚动,当这两个容器元素的内容都超出了容器高度,即都出现了滚动框的时候,如何在其中一个容器元素滚动时,让另外一个元素也随之滚动. 在一些支持用 ...
- Spotify敏捷模式详解三部曲第二篇:研发过程
本文转自:Scrum 中文网 引言 在本系列文章的第一篇,我们介绍了Spotify的敏捷研发团队,以及它独特的组织架构.Spotify的研发团队采用的是一种非常独特的组织架构,如下图所示: 整个研发组 ...
- layui日期与vue_详解Vue.js和layui日期控件冲突问题解决办法
详解Vue.js和layui日期控件冲突问题解决办法 发布于 2020-8-10| 复制链接 摘记: 事故还原: 今天在用layui的日期控件的时候发现一个问题,就是form表单中的日期选择之后,如果 ...
- CMake使用详解二(多文件编译)
文章目录 1 同一目录,多个源文件 1.1 创建2个源文件 1.2 编写CMakeLists.txt 1.3 编译项目 2 多个目录,多个源文件 2.1 在不同的目录下新建源代码 2.2 编写CMak ...
- CMake使用详解一(单文件编译)
文章目录 1 什么是CMake 2 单个源文件编译 2.1 编写单个cpp文件 2.2 编写CMakeLists.txt 2.3 使用`cmake .`和`make`编译项目 2.3.1 执行`cma ...
- java源码系列:HashMap底层存储原理详解——4、技术本质-原理过程-算法-取模具体解决什么问题
目录 简介 取模具体解决什么问题? 通过数组特性,推导ascii码计算出来的下标值,创建数组非常占用空间 取模,可保证下标,在HashMap默认创建下标之内 简介 上一篇文章,我们讲到 哈希算法.哈希 ...
- vue 加载页面时触发时间_详解Vue.js在页面加载时执行某个方法
详解Vue.js在页面加载时执行某个方法 jQuery中可以这样写 vue中,如果要达到相同效果,可以使用vue的生命周期函数,如create或者mounted 附上vue.js的生命周期函数执行流程 ...
- vue调用手机相机相册_详解Vue.js调用手机相机和相册以及上传
详解Vue.js调用手机相机和相册以及上传 发布于 2020-7-7| 复制链接 摘记: 组件 ```xhtml .. 组件 ```xhtml --> 选中{{imgList.length}}张 ...
- DDR3内存详解,存储器结构+时序+初始化过程
转载 DDR3内存详解,存储器结构+时序+初始化过程 2017-06-17 16:10:33 a_chinese_man 阅读数 23423更多 分类专栏: 硬件开发基础 转自:http://www. ...
最新文章
- C语言-程序运行效率总结及注意事项
- 闭包函数python_Python--函数对象闭包函数
- 新手对于iPhone开发环境等入门问题解答汇总
- Linux安装Oracle11G
- VTK:点定位器可视化用法实战
- yandexbot ip列表整理做俄罗斯市场的站长可以关注一下
- torch.backends.cudnn.benchmark--提升卷积神经网络的运行速度
- 注册Nocos配置中心失败:Could not resolve placeholder ‘config.info‘ in value “${config.info}
- 【论文写作】毕业论文写作的基本流程
- ENVI的seamless mosaic工具详解
- 文件上传—DiskFileItemFactory核心类
- python应聘要求_python爬取招聘要求等信息实例
- 外星人m15键盘灯光设置_Alienware Command Center灯光软件高级设置
- 2020-10-20 Java基础_定义和语法
- 8/3 MATLAB绘制正态分布概率密度函数(normpdf)图形
- 【数据挖掘】《数据分析与数据挖掘》--天津大学公开课
- Python-import
- oracle utl file grant,oracle中设置UTL_FILE_DIR参数
- 利用手机相机扫描文件和身份证件
- 游戏动画引擎 -(Crapell Game Engine Design - animation)