该代码将导致在foo()
全局范围内被评估为函数声明。由于您不调用它,因此结果QJSValue
是undefined
. 您可以通过在浏览器中打开 JavaScript 控制台并编写同一行来查看相同的行为:
你不能调用 的函数foo()
,undefined
因为它不存在。您可以做的是通过全局对象调用它:
这与您的 C++ 代码所看到的相同。因此,要访问和调用该foo()
函数,您需要通过以下的globalObject()函数访问它QJSEngine
:
#include <QCoreApplication>
#include <QtQml>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QJSEngine engine;
QJSValue evaluationResult = engine.evaluate("function foo() { return \"foo\"; }");
if (evaluationResult.isError()) {
qWarning() << evaluationResult.toString();
return 1;
}
if (!engine.globalObject().hasProperty("foo")) {
qWarning() << "Script has no \"foo\" function";
return 1;
}
if (!engine.globalObject().property("foo").isCallable()) {
qWarning() << "\"foo\" property of script is not callable";
return 1;
}
QJSValue callResult = engine.globalObject().property("foo").call();
if (callResult.isError()) {
qWarning() << "Error calling \"foo\" function:" << callResult.toString();
return 1;
}
qDebug() << "Result of call:" << callResult.toString();
return 0;
}
这段代码的输出是:
Result of call: "foo"
这与您发布的使用QScriptEngine
.
这种方法的好处是您无需触摸脚本即可使其工作。
不利的一面是,如果您打算重用相同的代码QJSEngine
来调用多个脚本,以这种方式编写 JavaScript 代码可能会导致问题,特别是如果其中的函数具有相同的名称。具体来说,您评估的对象将永远存在于全局命名空间中。
QScriptEngine
以以下形式解决了这个问题QScriptContext
:push()
在评估代码之前和pop()
之后的新上下文。但是,在QJSEngine
.
解决此问题的一种方法是为每个脚本创建一个新QJSEngine
脚本。我没试过,也不知道会贵到什么程度。
该文档看起来可能暗示了另一种解决方法,但我不太明白它如何与每个脚本的多个函数一起工作。
在与同事交谈后,我了解到一种使用对象作为接口来解决问题的方法:
#include <QCoreApplication>
#include <QtQml>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QJSEngine engine;
QString code = QLatin1String("( function(exports) {"
"exports.foo = function() { return \"foo\"; };"
"exports.bar = function() { return \"bar\"; };"
"})(this.object = {})");
QJSValue evaluationResult = engine.evaluate(code);
if (evaluationResult.isError()) {
qWarning() << evaluationResult.toString();
return 1;
}
QJSValue object = engine.globalObject().property("object");
if (!object.hasProperty("foo")) {
qWarning() << "Script has no \"foo\" function";
return 1;
}
if (!object.property("foo").isCallable()) {
qWarning() << "\"foo\" property of script is not callable";
return 1;
}
QJSValue callResult = object.property("foo").call();
if (callResult.isError()) {
qWarning() << "Error calling \"foo\" function:" << callResult.toString();
return 1;
}
qDebug() << "Result of call:" << callResult.toString();
return 0;
}
这段代码的输出是:
Result of call: "foo"
您可以在我刚刚链接到的文章中详细了解这种方法。这是它的摘要:
- 声明一个对象,每当您定义需要“导出”到 C++ 的内容时,您都可以向该对象添加属性。
- “模块函数”将其接口对象作为参数 (
exports
),允许函数外部的代码创建它并将其存储在变量 ( (this.object = {})
) 中。
但是,正如文章所述,这种方法仍然使用全局范围:
前面的模式通常被用于浏览器的 JavaScript 模块使用。该模块将声明一个全局变量并将其代码包装在一个函数中,以便拥有自己的私有命名空间。但是,如果多个模块碰巧声称相同的名称,或者如果您想同时加载一个模块的两个版本,这种模式仍然会导致问题。
如果您想更进一步,请按照文章进行到底。但是,只要您使用唯一的对象名称,就可以了。
这是一个“现实生活”脚本将如何改变以适应此解决方案的示例:
前
function activate(thisEntity, withEntities, activatorEntity, gameController, activationTrigger, activationContext) {
gameController.systemAt("WeaponComponentType").addMuzzleFlashTo(thisEntity, "muzzle-flash");
}
function equipped(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol-equipped.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
function unequipped(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
function destroy(thisEntity, gameController) {
}
后
( function(exports) {
exports.activate = function(thisEntity, withEntities, activatorEntity, gameController, activationTrigger, activationContext) {
gameController.systemAt("WeaponComponentType").addMuzzleFlashTo(thisEntity, "muzzle-flash");
}
exports.equipped = function(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol-equipped.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
exports.unequipped = function(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
exports.destroy = function(thisEntity, gameController) {
}
})(this.Pistol = {});
Car
脚本可以具有同名的函数(、activate
等destroy
),而不会影响Pistol
.
从 Qt 5.12QJSEngine
开始,支持正确的 JavaScript 模块:
对于更大的功能,您可能希望将代码和数据封装到模块中。模块是包含脚本代码、变量等的文件,并使用导出语句来描述其与应用程序其余部分的接口。在 import 语句的帮助下,一个模块可以引用其他模块的功能。这允许以安全的方式从较小的连接构建块构建脚本应用程序。相比之下,使用 evaluate() 的方法存在来自一个 evaluate() 调用的内部变量或函数意外污染全局对象并影响后续评估的风险。
所需要做的就是将文件重命名为具有.mjs
扩展名,然后像这样转换代码:
export function activate(thisEntity, withEntities, activatorEntity, gameController, activationTrigger, activationContext) {
gameController.systemAt("WeaponComponentType").addMuzzleFlashTo(thisEntity, "muzzle-flash");
}
export function equipped(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol-equipped.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
export function unequipped(thisEntity, ownerEntity) {
var sceneItemComponent = thisEntity.componentOfType("SceneItemComponentType");
sceneItemComponent.spriteFileName = ":/sprites/pistol.png";
var physicsComponent = thisEntity.componentOfType("PhysicsComponentType");
physicsComponent.width = sceneItemComponent.sceneItem.width;
physicsComponent.height = sceneItemComponent.sceneItem.height;
}
export function destroy(thisEntity, gameController) {
}
调用这些函数之一的 C++ 如下所示:
QJSvalue module = engine.importModule("pistol.mjs");
QJSValue function = module.property("activate");
QJSValue result = function.call(args);