【问题标题】:Defined property inside a JS decorator is not visibleJS 装饰器中定义的属性不可见
【发布时间】:2021-09-05 00:40:56
【问题描述】:

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

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();

为什么这些值不是唯一的?是否将关键属性添加到类(原型)级别而不是实例?装饰值是否类似于创建静态值?

【问题讨论】:

  • 我认为你不能在那种情况下使用@setter。如果您只记录instance,您将看到创建了一个对象Test,但没有分配任何值
  • 第二个代码中class Test的定义是什么?
  • @JaromandaX 同上。我没有改变它
  • 您的@setter 正在装饰Test.prototype.keyinstance 没有自己的 key 属性,它是继承的,这就是它没有出现在 JSON 中的原因。
  • 装饰器执行一次,将属性添加到Test.prototype

标签: javascript node.js typescript decorator typescript-decorator


【解决方案1】:

this similar postofficial TS documentation 中所述,属性装饰器无法访问对象实例,这意味着在您的示例中,target 不是该类的实例。

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

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 已经有二传手,仅供参考:https://www.typescripttutorial.net/typescript-tutorial/typescript-getters-setters/

【讨论】:

    【解决方案2】:

    问题的简短回答

    为什么 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 方法添加到类中,如下例所示:
        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. 如果装饰器不需要在属性级别,那么我们可以使用类装饰器:
    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 明确提及以下内容:

    注意  由于属性装饰器在 TypeScript 中的初始化方式,属性描述符不作为参数提供给属性装饰器。这是因为目前在定义原型成员时没有描述实例属性的机制,也没有办法观察或修改属性的初始值设定项。返回值也被忽略。因此,属性装饰器只能用于观察已为类声明了特定名称的属性。

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

    function setter(...args) {
      console.log(args);   // Outputs: [ TestArgs {}, 'key', undefined ]
    }
    
    class TestArgs {
      @setter
      public key: string;
    }
    
    const t = new TestArgs();
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-25
      • 2021-02-25
      • 2018-07-20
      • 2015-10-23
      • 2018-01-31
      • 2018-11-21
      • 2012-06-02
      • 1970-01-01
      相关资源
      最近更新 更多