在 JS 装饰器中定义的 属性 不可见

Defined property inside a JS decorator is not visible

我正在尝试编写一个装饰器来设置一个值,如下面的代码所示

function setter(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    value: 'Hello world',
    enumerable: true,
    configurable: true,
  });
}

class Test {
  @setter
  public key: string;
}

const instance = new Test();
console.log(instance.key);                // Outputs: 'Hello world'
console.log(JSON.stringify(instance));    // Outputs: '{}'

为什么 JSON.stringify 没有返回 { key: 'Hello world' } 而不是 {}

我已尝试进一步调试问题

function setter(target: any, propertyKey: string) {

  Object.defineProperty(target, propertyKey, {
    value: `Time -> ${new Date().getTime()}`,
    enumerable: true,
    configurable: true,
    writable: true,
  });
}

class Test {
  @setter
  public key: string;
}

async function main() {
  // Create a promise to wait 2 seconds
  const wait = new Promise((resolve) => {
    setTimeout(() => {
      resolve(true);
    }, 2000);
  });

  // Create a new instance
  const instance1 = new Test();
  // Wait
  await wait;
  // Create a second instance
  const instance2 = new Test();

  // Log the values of key in each instance
  console.log(instance1.key);  // Outputs 'Time -> 1624272363288'
  console.log(instance2.key);  // Outputs 'Time -> 1624272363288'
}

main();

为什么值不唯一?密钥 属性 是否已添加到 class(原型)级别而不是实例?装饰值是否类似于创建静态值?

and the official TS documentation 中所述,属性 装饰器无法访问对象实例,这意味着,在您的示例中,target 不是 class.

相反,您想使用第三个参数:(可能已经存在)descriptor 对象,它允许您定义 getter 和 setter 函数,这些函数又可以访问this(我在下面的代码中称它为self):

function setter(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  descriptor = descriptor || {};
  descriptor.get = function g(self: Test) { return self.x; };
  descriptor.set = function s(self: Test, value: any) { self.x = value; };
}

另请注意,TS 已经有 setters,仅供参考:https://www.typescripttutorial.net/typescript-tutorial/typescript-getters-setters/

问题简答

为什么 JSON.stringify 没有返回 { key: 'Hello world' } 而不是 {}?

修饰的 属性 被添加到原型中,因此它不会出现在 JSON.stringify() 的结果中。

这方面的一个例子:

function setter(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    value: 'Hello world',
    enumerable: true,
    configurable: true,
  });
}

class Test {
  @setter
  public key: string;
}

const instance = new Test();
console.log(instance.key);                    // Outputs: 'Hello world'
console.log(JSON.stringify(instance));        // Outputs: '{}'
console.log(Object.getPrototypeOf(instance)); // Outputs: "{ key: 'Hello world' }"

如何解决这个问题?

  1. 我们可以将 toJSON 方法添加到 class 中,如下例所示:
    function setter(target: any, propertyKey: string) {
      Object.defineProperty(target, propertyKey, {
        value: 'Hello world',
        enumerable: true,
        configurable: true,
      });
    }
    
    class Test {
      @setter
      public key: string;
    
      public a = 'A';
      public b = 'B';
    
      public toJSON() {
        return { a: this.a, b: this.b, key: Object.getPrototypeOf(instance).key };
      }
    }
    
    const instance = new Test();
    console.log(JSON.stringify(instance)); // Outputs: {"a":"A","b":"B","key":"Hello world"}
  1. 如果装饰器不需要在属性级别,那么我们可以使用class装饰器:
function setter(constructor) {
  return class extends constructor {
    key = 'update value';
    secret = 'set a new value';
  };
}

@setter
class Test {
  public key: string;
}

const instance = new Test();
console.log(JSON.stringify(instance));  
// Outputs: {"key":"update value","secret":"set a new value"

关于 属性 描述符的注释

documentation 明确提到了以下内容:

NOTE  A Property Descriptor is not provided as an argument to a property decorator due to how property decorators are initialized in TypeScript. This is because there is currently no mechanism to describe an instance property when defining members of a prototype, and no way to observe or modify the initializer for a property. The return value is ignored too. As such, a property decorator can only be used to observe that a property of a specific name has been declared for a class.

这意味着 3ed 参数在我们的例子中始终未定义。下面的代码说明了这一点:

function setter(...args) {
  console.log(args);   // Outputs: [ TestArgs {}, 'key', undefined ]
}

class TestArgs {
  @setter
  public key: string;
}

const t = new TestArgs();

正如您在上面看到的,本应是 属性 描述符的 3ed 参数未定义!