为什么将函数传递到元数据时未定义“this”以及如何修复它?

Why is `this` undefined when passing a Function into Metadata and how to fix it?

我想借助 Typescript 装饰器和 reflect-metadata 创建一个抽象。但是当我调用传递给元数据的函数时,this 未定义:

import "reflect-metadata";

const METHODS = "__methods__";
const Method = (obj: object) => {
  return function MethodDecorator(
    target: object,
    methodName: string | symbol,
    descriptor: PropertyDescriptor
  ) {
    const metadata = Reflect.getMetadata(METHODS, obj);

    Reflect.defineMetadata(
      METHODS,
      { ...metadata, [methodName]: descriptor.value },
      obj
    );
  };
};

const someObject: object = new Object();
class Main {
  private num = 42;

  constructor(other: Other) {
    other.init(someObject);
  }

  @Method(someObject)
  myMethod() {
    console.log("hello");
    console.log(this.num); // this is undefined (how can I fix it?)
  }
}

class Other {
  private methods: Record<string, Function> = {};

  init(obj: object) {
    this.methods = Reflect.getMetadata(METHODS, obj);
  }

  trigger(methodName: string) {
    this.methods[methodName]();
  }
}

const other = new Other();
new Main(other);
other.trigger("myMethod");

上面代码片段的输出是

hello
undefined

为什么 this 未定义,我该如何解决?


您可以通过克隆 this 示例存储库和 运行

自己尝试
yarn install
yarn start

那是因为你在这一行 return 使用 function 而不是箭头函数:

return function MethodDecorator(

这会创建一个新的上下文(函数作用域),这会导致 this 改为指向全局对象。 this 已定义:未定义的是 this.num,因为 window.num 不存在。

因此,如果您 return 将函数作为箭头函数,词法 this 将被保留,您将可以访问 this.num:

const Method = (obj: object) => {
  return (
    target: object,
    methodName: string | symbol,
    descriptor: PropertyDescriptor
  ) => {
    const metadata = Reflect.getMetadata(METHODS, obj);

    Reflect.defineMetadata(
      METHODS,
      { ...metadata, [methodName]: descriptor.value },
      obj
    );
  };
};

如果您通过将 this 的值传递给 other.init 来保存它,如下所示,然后将该值绑定到每个方法,它将起作用。不幸的是,它 this 直接传递给装饰器,尽管那样会更干净。

const someObject: object = new Object();
class Main {
  private num = 42;

  constructor(other: Other) {
    other.init(someObject, this);
  }

  @Method(someObject)
  myMethod() {
    console.log("hello");
    console.log(this.num); // 42
  }
}

class Other {
  private methods: Record<string, Function> = {};

  init(obj: object, thisArg: object) {
    this.methods = Reflect.getMetadata(METHODS, obj);
    Object.keys(this.methods).forEach((method) => {
      this.methods[method] = this.methods[method].bind(thisArg);
    })
  }

  trigger(methodName: string) {
    this.methods[methodName]();
  }
}

const other = new Other();
new Main(other);
other.trigger("myMethod");