在 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' }"
如何解决这个问题?
- 我们可以将
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"}
- 如果装饰器不需要在属性级别,那么我们可以使用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 参数未定义!
我正在尝试编写一个装饰器来设置一个值,如下面的代码所示
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(原型)级别而不是实例?装饰值是否类似于创建静态值?
如 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' }"
如何解决这个问题?
- 我们可以将
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"}
- 如果装饰器不需要在属性级别,那么我们可以使用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 参数未定义!