作用域和命名解析

范围和命名解析的概述

QML属性绑定、内联函数和导入的JavaScript文件都在JavaScript作用域中运行。作用域控制表达式可以访问哪些变量,以及当两个或多个名称冲突时哪个变量优先。

由于JavaScript的内置作用域机制非常简单,QML对其进行了增强,以更自然地适应QML语言扩展。

JavaScript 作用域

QML的范围扩展不会干扰JavaScript的自然作用域。JavaScript程序员在QML中编程函数、属性绑定或导入的JavaScript文件时,可以重用他们现有的知识。

在以下示例中,addConstant() 方法将向传递的参数添加13,正如程序员所期望的那样,无论QML对象的ab属性的值如何。

QtObject {
    property int a: 3
    property int b: 9

    function addConstant(b) {
        var a = 13;
        return b + a;
    }
}

QML 尊重 JavaScript 的正常作用域规则,即使在绑定中也适用。这个完全邪恶、令人厌恶的绑定将会把 12 赋值给 QML 对象的 a 属性。

QtObject {
    property int a

    a: { var a = 12; a; }
}

在QML中,每个JavaScript表达式、函数或文件都有其自己独特的变量对象。在一个中声明的局部变量永远不会与在另一个中声明的局部变量发生冲突。

类型名称和导入的JavaScript文件

QML 文档 包含定义文档可见的类型名称和 JavaScript 文件的导入语句。除了在 QML 声明本身中使用外,类型名称还由 JavaScript 代码在访问 附加属性 和枚举值时使用。

导入的效果适用于QML文档中的每个属性绑定和JavaScript函数,即使是嵌套的内联组件中的那些。以下示例展示了一个简单的QML文件,该文件访问了一些枚举值并调用了导入的JavaScript函数。

import QtQuick 2.0
import "code.js" as Code

ListView {
    snapMode: ListView.SnapToItem

    delegate: Component {
        Text {
            elide: Text.ElideMiddle
            text: "A really, really long string that will require eliding."
            color: Code.defaultColor()
        }
    }
}

绑定范围对象

具有属性绑定的对象被称为绑定的范围对象。在以下示例中,Item对象是绑定的范围对象。

Item {
    anchors.left: parent.left
}

绑定可以直接访问作用域对象的属性,无需限定。在前面的例子中,绑定直接访问了Item的parent属性,而不需要任何形式的前缀。QML引入了一种更结构化、面向对象的JavaScript方法,因此不需要使用JavaScript的this属性。

在使用绑定访问附加属性时必须小心,因为它们与范围对象的交互。从概念上讲,附加属性存在于所有对象上,即使它们只对其中一部分对象产生影响。因此,未限定的附加属性读取将始终解析为范围对象上的附加属性,这并不总是程序员所期望的。

例如,PathView 类型根据其委托在路径中的位置附加插值属性。由于 PathView 仅将这些属性有意义地附加到委托中的根对象,因此任何访问这些属性的子对象都必须明确限定根对象,如下所示。

PathView {
    delegate: Component {
        Rectangle {
            id: root
            Image {
                scale: root.PathView.scale
            }
        }
    }
}

如果Image对象省略了root前缀,它将无意中访问自身未设置的PathView.scale附加属性。

组件范围

QML文档中的每个QML组件都定义了一个逻辑范围。每个文档至少有一个根组件,但也可以有其他内联子组件。组件范围是组件内对象ID和组件根对象属性的联合。

Item {
    property string title

    Text {
        id: titletype
        text: "<b>" + title + "</b>"
        font.pixelSize: 22
        anchors.top: parent.top
    }

    Text {
        text: titletype.text
        font.pixelSize: 18
        anchors.bottom: parent.bottom
    }
}

上面的例子展示了一个简单的QML组件,它在顶部显示了一个富文本标题字符串,并在底部显示了相同文本的较小副本。第一个Text类型在形成要显示的文本时直接访问组件的title属性。根类型的属性可以直接访问,这使得在整个组件中分发数据变得非常简单。

第二种Text类型使用id直接访问第一个文本。ID由QML程序员明确指定,因此它们始终优先于其他属性名称(除了JavaScript Scope中的属性)。例如,在前面的例子中,即使绑定scope object有一个titletype属性,titletype id仍然会优先。

组件实例层次结构

在QML中,组件实例将它们的组件作用域连接在一起,形成一个作用域层次结构。组件实例可以直接访问其祖先的组件作用域。

展示这一点最简单的方法是使用内联子组件,这些子组件的组件范围隐式地作为外部组件的子范围。

Item {
    property color defaultColor: "blue"

    ListView {
        delegate: Component {
            Rectangle {
                color: defaultColor
            }
        }
    }
}

组件实例层次结构允许委托组件的实例访问Item类型的defaultColor属性。当然,如果委托组件有一个名为defaultColor的属性,那么该属性将优先。

组件实例的作用域层次结构也扩展到外部组件。在以下示例中,TitlePage.qml 组件创建了两个 TitleText 实例。即使 TitleText 类型位于单独的文件中,当它在 TitlePage 中使用时,仍然可以访问 title 属性。QML 是一种动态作用域语言 - 根据其使用位置,title 属性的解析方式可能会有所不同。

// TitlePage.qml
import QtQuick 2.0
Item {
    property string title

    TitleText {
        size: 22
        anchors.top: parent.top
    }

    TitleText {
        size: 18
        anchors.bottom: parent.bottom
    }
}

// TitleText.qml
import QtQuick 2.0
Text {
    property int size
    text: "<b>" + title + "</b>"
    font.pixelSize: size
}

动态作用域非常强大,但必须谨慎使用,以防止QML代码的行为变得难以预测。一般来说,它应该只在两个组件已经以其他方式紧密耦合的情况下使用。在构建可重用组件时,最好使用属性接口,如下所示:

// TitlePage.qml
import QtQuick 2.0
Item {
    id: root
    property string title

    TitleText {
        title: root.title
        size: 22
        anchors.top: parent.top
    }

    TitleText {
        title: root.title
        size: 18
        anchors.bottom: parent.bottom
    }
}

// TitleText.qml
import QtQuick 2.0
Text {
    property string title
    property int size

    text: "<b>" + title + "</b>"
    font.pixelSize: size
}

重写的属性

QML 允许在对象声明中定义的属性名称被另一个扩展第一个对象声明的对象声明中的属性覆盖。例如:

// Displayable.qml
import QtQuick 2.0
Item {
    property string title
    property string detail

    Text {
        text: "<b>" + title + "</b><br>" + detail
    }

    function getTitle() { return title }
    function setTitle(newTitle) { title = newTitle }
}

// Person.qml
import QtQuick 2.0
Displayable {
    property string title
    property string firstName
    property string lastName

    function fullName()  { return title + " " + firstName + " " + lastName }
}

在这里,名称 title 被赋予了 Displayable 的输出文本标题,同时也被赋予了 Person 对象的尊称。

重写的属性根据其引用的范围进行解析。在Person组件的范围内,或从引用Person组件实例的外部范围中,title解析为在Person.qml中声明的属性。fullName函数将引用在Person内部声明的title属性。

然而,在Displayable组件内部,title指的是在Displayable.qml中声明的属性。getTitle()和setTitle()函数,以及Text对象的text属性的绑定,都将引用在Displayable组件中声明的title属性。

尽管共享相同的名称,这两个属性是完全独立的。其中一个属性的onChanged信号处理程序不会因为另一个同名属性的更改而触发。对任一属性的别名将引用其中一个,而不是两者。

JavaScript 全局对象

QML 禁止与全局对象上的属性冲突的类型、id 和属性名称,以防止任何混淆。程序员可以确信 Math.min(10, 9) 将始终按预期工作!

请参阅JavaScript Host Environment以获取更多信息。