原文  QML Engine Internals, Part 2: Bindings

译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好者和工作者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来。翻译并不是完全直译,有不足之处,请指正,谢谢!

———————————————————————————————————————————

上一篇:QML文件加载     下一篇 绑定类型

该博文是深入解析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 QML文件例子

在这个例子中,包含了给属性赋值的两种方式:

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创建的:

 
图2 创建绑定的VME指令

编译后绑定是一种优化的绑定,我们还是先研究一下普通绑定,它是由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属性是如何被访问的:

 
图3 属性访问函数调用堆栈

从上面的堆栈信息来看,我们发现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)的更多相关文章

  1. 深入解析QML引擎, 第3部分: 绑定类型

    原文 QML Engine Internals, Part 3: Binding Types 译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/Q ...

  2. 深度解析qml引擎---(2)绑定(binding)

    强烈的希望是人生中比任何欢乐更大的兴奋剂.--尼采 上一篇文章讲了QML引擎加载qml文件的过程,大体过程是,解析qml文件,然后为文件中的每个元素创建对应的c++对象.例如,qml文件中如果使用了T ...

  3. 深入解析QML引擎, 第1部分:QML文件加载

    译者注:这个解析QML引擎的文章共4篇,分析非常透彻,在国内几乎没有找到类似的分析,为了便于国内的QT/QML爱好者和工作者也能更好的学习和理解QML引擎,故将这个系列的4篇文章翻译过来.翻译并不是完 ...

  4. 深入解析QML引擎, 第4部分: 自定义解析器

    原文 QML Engine Internals, Part 4: Custom Parsers ——————————————————————————————————————————— 上一篇 绑定类型 ...

  5. 深度解析qml引擎---(1)Qml文件加载

                                                                        "美的事物是永恒的喜悦" --- 济慈    ...

  6. QML引擎的演进,第一部分

    原文链接:Lars Knoll – Evolution of the QML engine, part 1 QML作为一项技术对于Qt的成功变得越来越重要.它允许创建流畅的动画界面,与现今的市场预期相 ...

  7. MyBatis源码解析(十二)——binding绑定模块之MapperRegisty

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为 ...

  8. 腾讯QQ团队开源分布式后台毫秒服务引擎全解析:引擎架构、RPC、灰度……

    腾讯QQ团队将于12月4日开源一个服务开发运营框架,叫做毫秒服务引擎(Mass Service Engine in Cluster,MSEC),它集RPC.名字发现服务.负载均衡.业务监控.灰度发布. ...

  9. 视图模板引擎——Vue【双向绑定】原理剖析

    首先我们来了解一下MVC.MVP.MVMM这三大架构模式在前端角度上的理解. MVC分别是 Model(模型).View(视图).Controller(控制器)三个模块.View(视图层)最主要完成前 ...

随机推荐

  1. Calabash(葫芦娃)

    嘟嘟嘟 第一眼就觉得肯定某种是最短路,然后想了半天也不知道.然后就把送的50分写了,然后就爆搜,结果因为一个错误的剪枝竟然90分?!只能怪数据太水…… 考完试后听bin哥讲,说就是普通的最短路,只不过 ...

  2. selenium + python自动化测试unittest框架学习(四)python导入模块及包知识点

    在写脚本的时候,发现导入某些模块,经常报错提示导入模块失败,这里来恶补下python导入模块的知识点. 1.模块导入时文件查找顺序 在脚本中,import xxx模块时的具体步骤: (1)新建一个mo ...

  3. sql连接查询INNER JOIN,LEFT JOIN,RIGHT JOIN区别

    测试表 课程表cource 教师表teacher 查询老师教的课程 1.inner join内链接 ,只显示完全匹配的查询结果 SELECT  cource.cname,teacher.tname f ...

  4. Centos 7下Nagios的安装及配置

    简介 Nagios 是一款自动化运维工具,可以协助运维人员监控服务器的运行状况,并且拥有报警功能.本文章将介绍其安装方法和详细的配置方法. nagios 监控服务应用指南 本地资源:负载,CPU,磁盘 ...

  5. 3.Spring Cloud初相识--------Ribbon客户端负载均衡

    前言: 在生产环境中,未避免单点故障,每个微服务都会做高可用部署. 通白的说,就是每一个一模一样的服务会根据需求提供多分在多台机器上. 那么在大并发的情况下,如何分配服务可以快速得到响应,就成为了我们 ...

  6. Order by排序

    asc 升序(默认),desc 降序 order by 后面  可以加 列.表达式.别名.序号(从1开始) desc; --表达式 年薪 from emp order by 年薪 desc; --别名 ...

  7. 【OC底层】OC对象本质,如 isa, super-class

    Objective-C的本质 1.我们编写的Objective-C,底层现实都是C/C++,代码生成步骤如下:   2.在OC中的所有面向对象的实现,都是基于C/C++的数据结构实现的 3.将Obje ...

  8. ibatis运行的SQL语句的输出——通过配置log4j

    将ibatis 的log4j运行级别调到DEBUG可以在控制台打印出ibatis运行的sql语句 ### 设置Logger输出级别和输出目的地 ###log4j.rootLogger=debug,st ...

  9. Codeforces Round #483 (Div. 2) B题

    B. Minesweeper time limit per test 1 second memory limit per test 256 megabytes input standard input ...

  10. TopJUI Combobox 联动

    这里给联动进行一个简单定义:因Combobox选择或输入的值发生改变时对自身或者其它组件产生影响称为联动.(注:editable确定是否可以手动输入) 有两种实现方法: 一.自己写对应的onChang ...