范围和命名解析#

范围和命名解析概述

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 作用域 中的那些之外)。例如,在不太可能的情况下,如果绑定的作用域对象 在前一个示例中有一个 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 主环境