V8 嵌入:class 中的方法 null `this` 继承自使用 Function::New 创建的方法

V8 embedding: null `this` from method inside class inheriting from one created using Function::New

我正在尝试使用自定义 class 嵌入 V8。它是为了在 JS 中扩展和使用。

在构造函数以外的方法中访问this时,为null。它应该能够为 class.

的那个实例保留数据

我是不是漏掉了什么?

JavaScript

import { Behavior } from '@giz/ecs';

class A extends Behavior {
  constructor(a) {
    super(a);

    this.a = 10;

    print(this); // this works
  }

  update() {
    print(this); // null
  }
}

A;

我是如何创作的Behavior

    Local<Module> ecsModule = Module::CreateSyntheticModule(
        isolate,
        String::NewFromUtf8(isolate, "@giz/ecs").ToLocalChecked(),
        {String::NewFromUtf8(isolate, "Behavior").ToLocalChecked()},
        [](Local<Context> context, Local<Module> module) -> MaybeLocal<Value>
        {
            auto isolate = context->GetIsolate();
            // behavior constructor right now is an empty function
            auto behavior = Function::New(context, behaviorConstructor).ToLocalChecked();

            module->SetSyntheticModuleExport(
                String::NewFromUtf8(isolate, "Behavior").ToLocalChecked(),
                behavior);

            return MaybeLocal<Value>(True(isolate));
        });
    ecsModule->InstantiateModule(context, ScriptingSystem::moduleResolutionCallback);

    // store the module for later use
    Global<Module> *globalModule = new Global<Module>();
    globalModule->Reset(isolate, ecsModule);
    modules["@giz/ecs"] = globalModule;

实例化class并调用更新函数

    v8::HandleScope handle_scope(isolate);
    // globalContext is a v8::Global<v8::Context> with the print function created
    v8::Local<Context> context = Local<Context>::New(isolate, globalContext);
    Context::Scope contextScope(context);

    // ...
    // create script origin, script source, instantiate module, ..
    // ...

    Local<Value> returnValue = module->Evaluate(context).ToLocalChecked();
    Local<Object> returnedBehavior = returnValue->ToObject(context).ToLocalChecked();

    Local<Value> arguments[1];
    arguments[0] = wrapEntity(*behavior->entity);

    // creates an instance of the behavior
    auto instance = returnedBehavior->CallAsConstructor(context, 1, arguments)
                        .ToLocalChecked()
                        ->ToObject(context)
                        .ToLocalChecked();

    // calls the update function
    instance->Get(context, String::NewFromUtf8(isolate, "update").ToLocalChecked())
        .ToLocalChecked()
        ->ToObject(context)
        .ToLocalChecked()
        ->CallAsFunction(context, Null(isolate), 0, nullptr);

谢谢。

问题在最后一行:

->CallAsFunction(context, Null(isolate), 0, nullptr);

CallAsFunction 的第二个参数是“接收者”,它是在被调用函数中可以作为 this 访问的东西:因为你在那里传递 Null(isolate)this 将是 null。尝试传递 instance
(本质上,您当前的代码相当于 let instance = new A(); instance.update.call(null);。)


旁注:请注意,如果之前的操作抛出异常,ToLocalChecked 将使您的进程崩溃。将 MaybeLocals 显式转换为 Locals 的要求已被引入,目的是指出 C++ 代码应检查异常并以某种方式处理它们的所有位置。只有在您可以保证操作不会抛出的极少数情况下(例如分配一个短字符串),使用 ToLocalChecked 才是安全的。当前会使您的进程崩溃的示例:

  • A 的构造函数抛出
  • A的构造函数returnsnull
  • A 的构造函数为 update 安装一个 getter 抛出
  • A 的构造函数为 update 安装一个 getter,returns null

一个常见的模式是:

Local<...> value;
if (!FunctionThatReturnsMaybeLocal(...).ToLocal(&value)) {
  /* Handle error: show message, skip silently, ... */
  return;
}
// Now work with {value}.

(是的,从 C++ 使用 JavaScript 很困难。)