整合 C++ 中的 JavaScript 值
以下类可用来从 C++ 代码加载和访问 JavaScript:
- QJSValue,作为 Qt/JavaScript 数据类型存储的容器。
- QJSManagedValue,表示属于 QJSEngine 的 JavaScript 堆中的值。
- QJSPrimitiveValue,在 JavaScript 语义上操作原生类型。
使用 QJSValue 在引擎之间传输值,并使用 QJSManagedValue 与 JavaScript 值交互。只有在你需要模拟 C++ 中 JS 原始值的语义时才使用 QJSPrimitiveValues。
QJSValue | QJSManagedValue | QJSPrimitiveValue |
---|---|---|
持久存储值 | 短暂值 | 短暂值 |
将值传输到/从引擎 | 访问属性 | 仅限原生类型 |
调用方法 | 基本算术和比较 |
QJSValue 作为容器类型
QJSValue 存储了 ECMAScript 中支持的数据类型,包括函数、数组以及所有由 QVariant 支持的对象类型。作为一个容器,它可以用作将值传递到 QJSEngine 并接收值的媒介。
class Cache : public QObject { Q_OBJECT QML_ELEMENT public: Q_INVOKABLE QJSValue lookup(const QString &key) { if (auto it = m_cache.constFind(key); it != m_cache.constEnd()) { return *it; // impicit conversion } else { return QJSValue::UndefinedValue; // implicit conversion } } QHash<QString, QString> m_cache; }
在缓存未命中时,返回 undefined
。否则,返回缓存的值。请注意,当返回值时会发生隐式转换(分别从 QString 和 QJSValue::SpecialValue)。
QJSValue 也有一个 API 用于与包含的值交互,但推荐使用 QJSManagedValue。
原生和托管值
QJSValue 和 QJSManagedValue 存储的值可以是托管或原生。在 QML 的 JS 引擎中,托管值可以看作是指向堆上数据结构的指针,其内存由引擎的垃圾收集器管理。原始值的内容是直接存储的,使用纳米盒技术,即使只需要两种方式来表示 NaN 值(一个是用于信号,另一个是用于静默 NaN 值),也可以用多种方式表示。
从托管值中可以获取到引擎的指针,但不能从原始值中获取。当使用 QJSValue 的 JavaScript API 时,需要访问到引擎来评估 JavaScript。例如,要运行 call(args)
函数,必须在引擎中对其进行解释。这是有效的,因为这个函数是一个托管值,你可以从它来获得引擎。
同样,在你调用函数或在原始数字或字符串上访问属性时需要引擎。每次在原始值上调用方法时,都会创建其对应非原始对象的实例。这被称为装箱。当你编写 (42).constructor
时,这等同于 (new Number(42)).constructor
,并返回全局数字对象的构造函数。因此,如果你编写 QJSValue(42).property("constructor")
,你期望得到包含该函数的 QJSValue。然而,你得到的是一个包含 undefined
的 QJSValue。
你构造的 QJSValue 中只包含原始值,因此你不能访问到引擎。你也不能简单地将原始值在 QJSEngine 中的属性查找硬编码,因为你可能在某个引擎中将 Number.prototype.constructor.additionalProperty = "the Spanish Inquisition" 设置,而在另一个中是 Number.prototype.constructor.additionalProperty = 42。最终结果将是出乎意料的。
为了确保属性访问始终有效,你需要在 QJSValue 中总是存储装箱值,或者存储指向引擎的额外指针。
然而,这将与 QJSValue 当前的使用方式不兼容,会在传递原始值时导致不必要的 JS 堆分配,并增加存储 QJSValue 所需的大小。因此,你应该只使用 QJSValue 进行存储,并使用 QJSManagedValue 来获得引擎。
QJSManagedValue
QJSManagedValue 与 QJSValue 类似,但有一些区别
- 构造函数(除了默认和移动构造函数2之外)需要传递一个 QJSEngine 指针。
- 添加了类似于 deleteProperty 和 isSymbol 的方法。
- 如果 QJSManagedValue 方法遇到异常,它们将保持原样。
要在代码中获取引擎,你可能处于一个脚本上下文,已经可以通过 QJSEngine::newObject
创建新对象,并通过 QJSEngine::evaluate
求解表达式来访问引擎;或者你希望在已经与引擎注册的 QObject 中评估一些 JavaScript。在后一种情况下,你可以使用 qjsEngine(this)
来获取当前活动的 QJSEngine。
QJSManagedValue 还提供了一些在 QJSEngine 中没有等效方法的方法。
在下面的示例中,QJSManagedValue 方法遇到异常,并使用 QJSEngine::catchError 来处理异常。
QJSEngine engine; // We create an object with a read-only property whose getter throws an exception auto val = engine.evaluate("let o = { get f() {throw 42;} }; o"); val.property("f"); qDebug() << engine.hasError(); // prints false // This time, we construct a QJSManagedValue before accessing the property val = engine.evaluate("let o = { get f() {throw 42;} }; o"); QJSManagedValue managed(std::move(val), &engine); managed.property("f"); qDebug() << engine.hasError(); // prints true QJSValue error = engine.catchError(); Q_ASSERT(error.toInt(), 42);
然而,在一个已注册对象的内部方法中,你可能想让异常沿着调用堆栈向上冒泡。
应临时在堆栈上创建QJSManagedValue,一旦您不再需要对包含的值进行操作,就应该将其丢弃。由于QJSValue以更高效的方式存储原始值,因此,不建议将QJSManagedValue用作函数的返回或参数类型,或者属性的类型,因为引擎不会对它进行特殊处理,并且不会将值转换为它(与QJSValue相反)。
QJSPrimitiveValue
QJSPrimitiveValue可以存储任何原始类型,并根据ECMA-262标准支持算术运算和比较。与总是通过引擎的QJSManagedValue相比,它允许在原始值上执行低开销操作,同时仍然产生与引擎返回不可区分的结果。由于QJSPrimitiveValue相对较大,不建议存储值。
© 2024 The Qt Company Ltd. 此处包含的文档贡献的著作权属于其各自的拥有者。此处提供的文档根据Free Software Foundation发布的GNU自由文档许可协议版本1.3的条款授权。Qt及其相应的标志是The Qt Company Ltd.在芬兰和/或世界其他国家的商标。所有其他商标均为其各自所有者的财产。