深入解析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(视图层)最主要完成前 ...
随机推荐
- Win2008 r2 IIS7.5出现“FastCGI进程最近常常失败。请过一会再尝试此请求”的解决方法
错误信息如图所示: 应用程序“PIAOYUN.CC”中的服务器错误Internet Information Services 7.5 错误摘要HTTP 错误 500.0 - Internal Serv ...
- Presentation 常用的承接句——技术分享、学术报告串联全场不尴尬
前言 现在即使是搞技术,做科研的,也需要在不同的场合,用ppt来做分享,做汇报,做总结. 如果国际会议,研讨会,或者在外企,国外工作,英文的presentation就更加必不可少.英语的提升需要大家从 ...
- 31、springboot与任务
异步任务 测试如下: 进行等待三秒在进行应答 @Service public class AsynService { public void hello(){ try { Thread.sleep() ...
- ES6标准入门 字符串的扩展
1:模板字符串与模板引擎 https://blog.csdn.net/crper/article/details/52940625 es6模板字符串中标签模板作为参数时产生空元素的问题 https:/ ...
- STM8 亮灯程序
开发环境:ST Visual Develop+STM32 ST-LINK Utility+开发板 原理:定时向指定针脚输出高电平信号 /* MAIN.C file * * Copyright (c) ...
- PAT——1065. 单身狗
“单身狗”是中文对于单身人士的一种爱称.本题请你从上万人的大型派对中找出落单的客人,以便给予特殊关爱. 输入格式: 输入第一行给出一个正整数N(<=50000),是已知夫妻/伴侣的对数:随后N行 ...
- 【luogu P2827 蚯蚓】 题解
题目链接:https://www.luogu.org/problemnew/show/P2827 35分:暴力sortO(mnlogn). 80分:考虑到每次不好维护不被切的点+q,正难则反.改成维护 ...
- ASP.NET MVC & WebApi 中实现Cors来让Ajax可以跨域访问 (转载)
什么是Cors? CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing).它允许浏览器向跨源服务器,发出XMLHttpReq ...
- fdfs上传图片成功在浏览器中访问不到404 Not Found
1.检查自己nginx配置文件,看是否有 user root这行 . 在nginx.conf文件里加一条:user root; 2.检查自己配置文件: storage.conf中的文件路径是否正确 ...
- 安装maven提示ERROR: JAVA_HOME is set to an invalid directory.
查询网上资料发现多种解决办法:有的是多写了分号,有的路径错误. 需要注意的是maven配置前需要配置好jdk的路径. 我的java_home 之前的配置为:C:\Program Files\Java\ ...