如何在 TypeScript 中为访问器方法创建“可枚举”装饰器

问题描述 投票:0回答:1

我正在尝试创建一个 @enumerable 装饰器,它将公开通过访问器方法定义的属性。

在类的实例上执行此操作的函数相当简单:

// This works great when called in the class constructor like ```makeEnumerable(this, ['prop1', 'prop2'])```
const makeEnumerable = (what: any, props: string[]) => {
  for (const property of props) {
    const descriptor = Object.getOwnPropertyDescriptor(what.constructor.prototype, property);
    if (descriptor) {
      const modifiedDescriptor = Object.assign(descriptor, { enumerable: true });
      Object.defineProperty(what, property, modifiedDescriptor);
    }
  }
};

但是,似乎不可能将其变成装饰器,因为它没有 实例

// Does not work for Object.keys, Object.getOwnPropertyNames or Object.entries
function enumerable (value: boolean = true): any {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): any {
    if (descriptor) {
      Object.assign(descriptor, { enumerable: value });
    }
  };
}

属性 does 仍然在

for (const x in y)
结构中枚举(奇怪的是),但没有其他地方 - 更糟糕的是,
Object.entries
会抛出错误。

这是使用上述函数的示例:

class MyClass {
  #privateVal1: any;
  #privateVal2: any;

  constructor () {
    makeEnumerable(this, ['b']);
  }

  @enumerable(true)
  get a () {
    return this.#privateVal1;
  }

  set a (val: any) {
    this.#privateVal1 = val;
  }

  get b () {
    return this.#privateVal2;
  }

  set b (val: any) {
    this.#privateVal2 = val;
  }
}

const enumerableA = new MyClass();
enumerableA.a = 5;
enumerableA.b = 6;

const keys = [];
for (const key in enumerableA) {
  keys.push(key);
}

console.log({
  'forin': keys, // ['a', 'b']
  'keys': Object.keys(enumerableA), // ['b']
  'keys(proto)': Object.keys(Object.getPrototypeOf(enumerableA)), // ['a']
  'getOwnPropertyNames': Object.getOwnPropertyNames(enumerableA), // ['b']
  'getOwnPropertyNames(proto)': Object.getOwnPropertyNames(Object.getPrototypeOf(enumerableA)), // ['constructor', 'a', 'b']
});

console.log({
  'entries': Object.entries(enumerableA), // Error('Cannot read private member #privateVal1 from an object whose class did not declare it');
  'entries(proto)': Object.entries(Object.getPrototypeOf(enumerableA)), // Error('Cannot read private member #privateVal1 from an object whose class did not declare it');
});

有没有办法使用装饰器使访问器方法成为可枚举属性?

javascript typescript decorator accessor enumerable
1个回答
0
投票

这没什么奇怪的,你必须了解原型与实例属性

  • makeEnumerable
    在实例上设置可枚举描述符。
  • enumerable
    装饰器修改原型级描述符。

您期望

Object.keys(enumerableA)
成为
['a', 'b']
,就像
'forin': keys
一样,但是:

  • for...in
    循环迭代自己的和继承的可枚举属性。
  • 同时,
    Object.keys
    仅返回它自己的可枚举属性。

Enumerability and ownership of properties Enumerability and ownership of properties

查看此 MDN 博客以获取更多信息。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties#querying_object_properties

输出说明

for (const key in enumerableA)

  • 输出:
    ['a', 'b']
  • 为什么:
    • for...in
      循环迭代自己的和继承的可枚举属性
    • b
      a
      是可枚举的,但级别不同。
      b
      makeEnumerable
      作为实例属性进行枚举,而
      a
      原型属性由
      @enumerable
      装饰器进行枚举。

Object.keys(enumerableA)

  • 输出:
    ['b']
  • 为什么:
    • Object.keys
      仅列出 自己的可枚举属性
    • b
      通过构造函数中的makeEnumerable函数成为
      自己的可枚举属性
    • a
      仍在原型上,因此被排除在外。

Object.keys(Object.getPrototypeOf(enumerableA))

  • 输出:
    ['a']
  • 为什么:
    • 它仅列出原型上的可枚举属性,
      @enumerable
      装饰器修改
      a
      的原型级描述符。
    • b
      在原型上是不可枚举的,因为
      makeEnumerable
      函数仅在实例上可枚举。

Object.getOwnPropertyNames(enumerableA)

  • 输出:
    ['b']
  • 为什么:
    • 它列出了所有自己的属性(可枚举和不可枚举),但忽略原型属性。
    • b
      是实例自己的属性。

Object.getOwnPropertyNames(Object.getPrototypeOf(enumerableA))

  • 输出:
    ['constructor', 'a', 'b']
  • 为什么:
    • 这列出了直接在原型上定义的所有自己的属性(可枚举的和不可枚举的)。
    • constructor
      b
      是不可枚举的,但存在于原型上。

为什么
Object.entries
会抛出错误

Object.entries
访问所有可枚举的自有属性。

  • 查询时
    Object.entries(enumerableA)
    • 它访问
      b
      ,因为它是实例上的可枚举属性。访问
      b
      时,
      this
      get b() {...}
      上下文是
      MyClass { b: [Getter/Setter] }
      的实例。
    • 所以它工作正常,因为当 this 引用实例时,类中的私有属性是可以访问的。
  • 但是查询时
    Object.entries(Object.getPrototypeOf(enumerableA))
    • 它访问
      a
      ,因为
      a
      是原型上的可枚举属性。
    • 但是它抛出错误,因为
      this
      get a(){...}
      上下文是原型对象
      { a: [Getter/Setter] }
      ),而不是
      MyClass
    • 的实例

您必须了解 typescript 如何处理私有属性。

  • Typescript 会生成一些函数来在调用方法时检查
    this
    。如果
    this
    own class
    不同,则会抛出错误。
  • 检查已编译的 Javascript 代码以获取更多详细信息。

有没有办法使用装饰器使访问器方法成为可枚举属性?

,无法直接使用 Typescript 中的装饰器使 实例属性 可枚举,因为 Typescript 中的属性装饰器只能访问实例成员的

class prototype
,而不能访问实例本身。

  • Typescript 中的属性装饰器在创建类的任何实例之前执行。它在类定义级别运行。 如果您想让
  • instance properities
  • 可枚举,请使用
    makeEnumerable
    函数,就像您用于
    b
    那样。
    
    



我希望我已经解决了您的所有问题。如果您还有什么想澄清的,请随时询问。祝学习愉快!

© www.soinside.com 2019 - 2024. All rights reserved.