深入解析QML引擎, 第2部分: 绑定(Bindings)
原文 QML Engine Internals, Part 2: Bindings
译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好者和工作者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来。翻译并不是完全直译,有不足之处,请指正,谢谢!
———————————————————————————————————————————
该博文是深入解析QML引擎系列博文中的第二篇。在上一篇博文中,我们已经为大家揭示了QML引擎是如何加载QML文件的。简明扼要地回顾一下:解析QML文件,并为文件中的所有元素创建C ++对象。例如,QML文件中包含一个Text(文本)元素,QML引擎就会创建一个C ++ QQuickText类的实例。
QML引擎主要用于处理QML文件的加载,加载之后的运行时阶段就不再那么需要它了。运行时的事件处理和绘制等都是由它生成的C++类来完成的。例如,TextInput(文本输入)元素的输入事件是由QQuickTextInput::keyPressEven()处理,绘制则由QQuickTextInput::updatePaintNode()实现,完全不需要QML引擎参与。
但是在运行时,QML引擎依然会涉及到两个重要的东西:信号处理器和属性绑定更新(译者注:属性绑定更新,其实就是计算属性右边表达式的值,后续有详细的讲解,这个地方不用担心不明白。)。比如MouseArea的一个onClicked处理器就是信号处理器(译者注:MouseArea是第一篇博文的例子中的一个元素,从第一篇分析的内容,我们了解onClicked这种这样的信号处理器也被看作是属性值,和普通的属性没啥差别)。我们将在这篇文章中深入分析绑定(bindings)。在此之前请先看下面这个例子:

在这个例子中,包含了给属性赋值的两种方式:
1. 简单的赋一个值,比如给QQuickRectangle的width属性赋值300。对应的VME指令是STORE_DOUBL。它会在组件创建后执行,只是简单的调用函数QMetaObject::metacall(QMetaObject::WriteProperty,…), 该函数最终执行QQuickRectangle:setWidth()来设置width属性的值。在初始化之后,QML引擎再也不会修改width属性的值了。
2. 赋一个绑定(binding),比如给text属性赋一个绑定 "Window Area:" +(parent.width* parent.height),给anchors.centerIn属性赋一个绑定 parent。绑定的神奇之处在于,当Rectangle的height和width属性改变时,会自动更新到text属性。这是如何实现的呢?其实也没那么神奇,接下来我们将为你揭晓它的运作机制。(译者注:原作者说的binding到底是什么,下面马上就会揭晓,不用担心。)
创建绑定
通过设置QML_COMPILER_DUMP=1来输出VME指令,我们可以看到例子中的两个绑定都是由指令STORE_COMPILED_BINDING创建的:

编译后绑定是一种优化的绑定,我们还是先研究一下普通绑定,它是由STORE_BINDING指令创建的。查看QQmlVME::run()的代码,我们发现代码中创建了一个QQmlBinding对象,并把 "function $text() { return "Window Area:"+ (parent.width *parent.height) }" 做为它的表达式。没错,每一个绑定都是一个JavaScript函数!"function $text()" 这部分代码是由QML编译器添加的,这是因为QML的JavaScript引擎V8只支持完整的函数。这个JavaScript函数紧接着会被V8编译器编译成一个V8::Function对象。因为V8引擎有一个实时(JIT)编译器,所以它会生成本地的机器码(译者注:传统的JavaScript引擎是把JavaScript代码先编译为字节码,然后再通过解释器执行字节码,V8引擎运用JIT技术,不通过解释器执行字节码,而是直接把JavaScript代码编译成运行在CPU(x86/x64/ARM)上的机器码)。这时,V8:: Function对象并不会被执行,但是它会一直保留。
STORE_BINDING指令创建一个绑定可总结为:先创建了一个QQmlBinding对象,然后该对象借助V8引擎把传给它的JavaScript函数编译成了一个V8::Function对象。
(译者注:为了更容易理解后续的内容,在这里约定“绑定”即JavaScript函数,“绑定对象”即QQmlBinding对象,计算绑定的值即表示运行JavaScript函数求值,或者执行V8::Function代码求值)
运行绑定
在某些时候,绑定需要被运行,这意味着让V8引擎对绑定求值并将结果赋值给对应的属性。这些都是在创建阶段的最后阶段完成的。
QQmlVME::complete()会调用每个绑定对象的update()函数,在我们的例子中就是QQmlBinding:: update()函数。update()只是简单的执行v8:Function对象并将返回值赋给目标属性,这在我们的例子中就是Rectangle的text属性。
但是V8引擎是怎么知道parent.width和parent.height的值的呢?说实在的,它究竟是怎么知道parent对象的?答案就是:它不知道。V8引擎没有任何线索知道到底存在哪些对象,类名是什么,也不知道这些对象的属性是什么。当V8引擎遇到一个未知类或未知属性时,它会询问QML引擎中的一个对象包裹器(Object Wrapper),这个对象包裹器会找到正确的类或属性,并把他们返回给V8引擎。下面我们通过堆栈信息来看一看QQuickItem的width属性是如何被访问的:

从上面的堆栈信息来看,我们发现qv8qobjectwrapper.cpp中的包裹类最终调用函数QObject::qt_metacall(QMetaObject::ReadProperty,…) 来获取属性值。首先包裹类被V8代码调用,然后V8代码又被V8::Function对象对应的机器码调用。由于机器码没有堆栈帧(stack frames),因此GDB(调试工具)没法显示在??之后的堆栈信息。在上面的堆栈信息中我做了一点点假,其实它是由两个独立的堆栈信息拼起来的,细心的读者会发现,堆栈帧的序号并是不连续的。
由上可知,V8引擎会使用一个对象包裹类来获取属性值。同理,它会使用一个上下文包裹类来找到对象。例如,在我们的例子中,计算绑定值的过程中需要访问parent对象,就是通过这种方式来找到parent的。
综上所述:通过运行编译后的V8::Function代码来对绑定进行求值,再由V8引擎通过Qt里的包裹类来访问对象和属性,然后将求的值赋给目标属性。
更新绑定
好了,现在我们知道text属性是如何获得它的初始值的。但是绑定更新是如何实现的?当height和width属性改变时,QML引擎是怎么知道需要重新对绑定求值的呢?
这个问题的答案就隐藏在对象包裹类中。你应该还记得,当V8引擎需要访问一个属性时,就会调用它。这个对象包裹类不止返回属性值:它还会捕获所有被访问过的属性。从根本上讲,当一个属性被访问时,对象包裹类会调用绑定对象的捕获函数,在我们的例子中就是QQmlJavaScriptExpression::GuardCapture::captureProperty() (QQmlBinding是QQmlJavaScriptExpression的子类)。在捕获函数内部实现中,只是简单地把绑定对象的一个槽函数连接到被捕获属性的NOTIFY信号。当NOTIFY信号被触发时,与之连接的槽函数就会被调用,并重新计算绑定的值。如果你还没有听说过NOTIFY信号,也不用担心,这很简单:当一个属性用Q_PROPERTY来声明时,在那里就可能声明了一个NOTIFY信号。只要属性发生改变,拥有该属性的对象就会触发NOTIFY信号。比如,QQuickItem的width属性的声明类似如下:
Q_PROPERTY(qrealwidth READ width WRITE setWidth NOTIFY widthChanged)
在我们这个例子中,首次运行绑定,访问width属性时,该属性的捕获函数将绑定对象中的一个槽函数连接到widthChanged()信号。在此之后,只要QQuickItem触发widthChanged()信号,对应的槽函数将被调用,并重新计算绑定的值。
这就是为什么当你的属性发生改变时,拥有并触发NOTIFY信号是非常的重要。假如你忘了这样做,绑定的值就不会被重新计算,基本上,属性绑定就无法正确的运作。另一方面,尽管属性并没有真正地改变,但你也触发了NOTIFY信号,那么绑定的值也会被毫无意义地重新计算。
综上所述:当访问属性时,对象包裹类会调用绑定对象的捕捉函数,它会将绑定对象的一个槽函数连接到该属性的NOTIFY信号,以便当属性改变时重新计算绑定的值。
结论
在这篇博文中,我们已经深入分析绑定是如何工作的。总结成一句简短的话就是:每个绑定都是一个编译过的JavaScript函数,当任何一个引用的属性改变时,它将重新被计算。
我希望你喜欢阅读这些,我确信深入研究绑定的本质是非常有趣的。
在这个系列的下一篇博文中,我们将了解不同的绑定类型。现在,我们只研究了最基本的绑定,QQmlBinding,但我们知道还存在更多的绑定类型,比如编译后绑定。它们神秘的面纱即将被揭开,敬请关注!
作者:猿基地
链接:https://www.jianshu.com/p/9b277a3ee613
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
深入解析QML引擎, 第2部分: 绑定(Bindings)的更多相关文章
- 深入解析QML引擎, 第3部分: 绑定类型
原文 QML Engine Internals, Part 3: Binding Types 译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/Q ...
- 深度解析qml引擎---(2)绑定(binding)
强烈的希望是人生中比任何欢乐更大的兴奋剂.--尼采 上一篇文章讲了QML引擎加载qml文件的过程,大体过程是,解析qml文件,然后为文件中的每个元素创建对应的c++对象.例如,qml文件中如果使用了T ...
- 深入解析QML引擎, 第1部分:QML文件加载
译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好者和工作者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来.翻译并不是完 ...
- 深入解析QML引擎, 第4部分: 自定义解析器
原文 QML Engine Internals, Part 4: Custom Parsers ——————————————————————————————————————————— 上一篇 绑定类型 ...
- 深度解析qml引擎---(1)Qml文件加载
"美的事物是永恒的喜悦" --- 济慈 ...
- QML引擎的演进,第一部分
原文链接:Lars Knoll – Evolution of the QML engine, part 1 QML作为一项技术对于Qt的成功变得越来越重要.它允许创建流畅的动画界面,与现今的市场预期相 ...
- MyBatis源码解析(十二)——binding绑定模块之MapperRegisty
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为 ...
- 腾讯QQ团队开源分布式后台毫秒服务引擎全解析:引擎架构、RPC、灰度……
腾讯QQ团队将于12月4日开源一个服务开发运营框架,叫做毫秒服务引擎(Mass Service Engine in Cluster,MSEC),它集RPC.名字发现服务.负载均衡.业务监控.灰度发布. ...
- 视图模板引擎——Vue【双向绑定】原理剖析
首先我们来了解一下MVC.MVP.MVMM这三大架构模式在前端角度上的理解. MVC分别是 Model(模型).View(视图).Controller(控制器)三个模块.View(视图层)最主要完成前 ...
随机推荐
- ZooKeeper学习之路 (五)ZooKeeper API的简单使用 增删改查
zookeeper文件系统的增删改查 public class ZKDemo1 { private static final String CONNECT_STRING = "hadoop1 ...
- HTML data-* 自定义属性
HTML data-* 自定义属性 data-*是HTML5新添加的全局属性,通过它我们可以自定义属性,就像id,class等属性一样.我们可以使用JavaScript来操作这些属性. <div ...
- PHP面试系列 之Linux(四)---- Shell脚本
一.脚本执行方式 1.先赋予权限 chmod +x test.sh; 再直接执行 ./test.sh 2.调用解释器使得脚本执行 3.使用source命令 source test.sh 二.编写基础 ...
- ASP.Net GridView 基础 绑定字段
通过以前的学习,我们实现了效果如下: 现在我想修改显示/隐藏部分列,有两种做法: 一.在配置数据源的时候不是有查询哪些字段的吗,去除不需要的字段,重新绑定. 二.就是直接编辑列 下面是分析每种字段类型 ...
- PAT——1035. 插入与归并
根据维基百科的定义: 插入排序是迭代算法,逐一获得输入数据,逐步产生有序的输出序列.每步迭代中,算法从输入序列中取出一元素,将之插入有序序列中正确的位置.如此迭代直到全部元素有序. 归并排序进行如下迭 ...
- 【luogu P1606 [USACO07FEB]荷叶塘Lilypad Pond】 题解
题目链接:https://www.luogu.org/problemnew/show/P1606 这个题..第一问很好想,但是第二问,如果要跑最短路计数的话,零边权的花怎么办? 不如这样想,如果这个点 ...
- myeclipse调用loadrunner函数开发测试脚本
myeclipse调用loadrunner函数开发测试脚本 一.使用myeclipse开发性能测试脚本 1.使用Eclipse新建一个Java工程,将目录%LoadRunner_Home%\class ...
- GCD 多线程技术
Grand Central Dispatch(GCD)是异步执行任务的技术之一.一般将应用程序中记述的线程管理用 的代码在系统级中实现.开发者只需要定义想执行的任务并追加到适当的Dispatch Qu ...
- list 去重复元素
public static List removeDuplicate(List list){ List listTemp = new ArrayList(); for(int i=0;i<lis ...
- elementUi + express 上传图片
// 前端代码 <el-upload drag action="http://localhost:4001/article/uploadCoverImage" multipl ...