打字稿:装饰器在 angular 项目和打字稿游乐场上的行为不同
Typescript: decorators behave differently on angular project and typescript playground
我需要在 angular 2.0.0-rc1 中将一个对象序列化为 json,当我发现 Typescript 的 private 根本不是私有的,并设置 属性不通过JSON.stringify.
输出
所以我着手装饰class:
//method decorator
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
//property decorator
function exclude(target: any, propertyKey: string): any {
return { enumerable: false };
}
class MyClass {
test: string = "test";
@exclude
testExclude: string = "should be excluded";
@enumerable(true)
get enumerated(): string {
return "yes";
}
@enumerable(false)
get nonEnumerated(): string {
return "non enumerable"
}
}
let x = new MyClass();
//1st
console.log(JSON.stringify(x));
//2nd
console.log(JSON.stringify(x, Object.keys(MyClass.prototype)));
//3rd
console.log(JSON.stringify(x, Object.keys(x).concat(Object.keys(MyClass.prototype))));//test 3
在 Typescript playground 上,这给出了
{"test":"test"}
{"enumerated":"yes"}
{"test":"test","enumerated":"yes"}
但是在我的项目 (angular 2.0.0-rc1) 上,这给出了
{"test":"test","testExclude":"should be excluded"}
{"enumerated":"yes"}
{"test":"test","testExclude":"should be excluded","enumerated":"yes"}
我真正想要的是 playground 的输出#3。
看了转译后的代码后,
唯一的区别是反射元数据的代码:
//snip ...
__decorate([
exclude,
__metadata('design:type', String)
], MyClass.prototype, "testExclude", void 0);
__decorate([
enumerable(true),
__metadata('design:type', String)
], MyClass.prototype, "enumerated", null);
__decorate([
enumerable(false),
__metadata('design:type', String)
], MyClass.prototype, "nonEnumerated", null);
return MyClass;
}());
none 操场上 __metadata
行。
这里发生了什么?我怎样才能在我的项目中获得 playground 的第 3 名?
已修复(或者可能只是一种解决方法)。
请注意,在 playground 中,Reflect-metadata 不可用。 属性 装饰器可以 return 一个对象被赋值(或)到描述符来改变它的行为。在 angular 环境中,Reflect-metadata(特别是 Reflect.decorate()
)被用来装饰事物。
阅读 reflect-metadata doc, and this 后,显然无法更改 属性 装饰器上的 属性Descriptor,因为它与构造函数而不是原型相关联。解决方案(解决方法)是使用新描述符重新创建 属性。
function include(value: boolean) {
return function (target: any, propertyKey: string): any {
// Buffer the value
var _val = target[propertyKey];
// Delete property.
if (delete target[propertyKey]) {
// Create new property with getter and setter
Object.defineProperty(target, propertyKey, {
get: () => _val,
set: (newVal) => _val = newVal,
enumerable: value,
configurable: true
});
}
}
}
只需要工厂,所以我可以使用 @include(false)
而不是 @exclude
。
唯一的缺点是 属性 现在绑定到原型,因此正常的 JSON.stringify(instance)
不会序列化它。
关于这一点,我们可以进一步使通用装饰器在 属性 和方法中都可用,例如:
//method decorator
function excludeMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = false;
return descriptor;
};
//property decorator
function excludeProperty(target: any, propertyKey: string): any {
// Buffer the value
var _val = target[propertyKey];
// Delete property.
if (delete target[propertyKey]) {
// Create new property with getter and setter
Object.defineProperty(target, propertyKey, {
get: () => _val,
set: (newVal) => _val = newVal,
enumerable: false,
configurable: true
});
}
}
function exclude(...args : any[]) {
switch(args.length) {
case 2:
return excludeProperty.apply(this, args);
case 3:
if (typeof args[2] !== "number")
return excludeMethod.apply(this, args);
default:
throw new Error("Decorators are not valid here!");
}
}
所以现在我们可以这样使用它了:
class MyClass {
test: string = "test";
@exclude
testExclude: string = "should be excluded";
get enumerated(): string {
return "yes";
}
@exclude
get nonEnumerated(): string {
return "non enumerable"
}
constructor() {}
}
let x = new MyClass();
//to serialize, we have to whitelist the instance and its prototype prop keys
console.log(JSON.stringify(x, Object.keys(x).concat(Object.keys(MyClass.prototype))));
到目前为止,我还没有找到更简洁的方法。
我掉进了兔子洞...
所以出于某种原因,将白名单添加到 JSON.stringify 以某种方式使其无法递归序列化嵌套对象:
class a {
p1 = 1;
p2 = 2;
}
class b {
m1 = new a();
m2 = "test";
m3 = new Array<a>();
}
let i = new b();
i.m3.push(new a());
i.m3.push(new a());
JSON.stringify(i);
// properly gives
// {"m1":{"p1":1,"p2":2},"m2":"test","m3":[{"p1":1,"p2":2},{"p1":1,"p2":2}]}
JSON.stringify(i, Object.keys(i).concat(Object.keys(Object.getPrototypeOf(i))));
// nested class a doesn't get serialized
// {"m1":{},"m2":"test","m3":[{},{}]}
如果你像我一样想在 TS 中隐藏私有变量并给它一个只读外观,那么把它放在那里 属性:
将它声明为一个简单的对象成员,然后在构造函数中修改它的属性描述符:
//Don't do this
class a {
private _prop;
get prop() { return _prop; }
}
//do this instead
class a {
prop; //just define your public-facing property
constructor() {
let _prop; //internal variable here
Object.defineProperty(this, "prop", { //now we modify the existing prop,
get: () => _prop, //closure to outside variable
//(no set here, it's readonly after all)
enumerable: true, //make sure it's visible
configurable: false //close up access
});
}
}
现在我们可以简单地使用 JSON.stringify(instance)
。唯一的缺点是如果你有复杂的 getter/setter,请记住它在每个 instance/new.
中被调用
使用此模式和上面的 @exclude 装饰器,几乎可以解决我的用例。希望这对某人有所帮助..
我需要在 angular 2.0.0-rc1 中将一个对象序列化为 json,当我发现 Typescript 的 private 根本不是私有的,并设置 属性不通过JSON.stringify.
输出所以我着手装饰class:
//method decorator
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
};
}
//property decorator
function exclude(target: any, propertyKey: string): any {
return { enumerable: false };
}
class MyClass {
test: string = "test";
@exclude
testExclude: string = "should be excluded";
@enumerable(true)
get enumerated(): string {
return "yes";
}
@enumerable(false)
get nonEnumerated(): string {
return "non enumerable"
}
}
let x = new MyClass();
//1st
console.log(JSON.stringify(x));
//2nd
console.log(JSON.stringify(x, Object.keys(MyClass.prototype)));
//3rd
console.log(JSON.stringify(x, Object.keys(x).concat(Object.keys(MyClass.prototype))));//test 3
在 Typescript playground 上,这给出了
{"test":"test"}
{"enumerated":"yes"}
{"test":"test","enumerated":"yes"}
但是在我的项目 (angular 2.0.0-rc1) 上,这给出了
{"test":"test","testExclude":"should be excluded"}
{"enumerated":"yes"}
{"test":"test","testExclude":"should be excluded","enumerated":"yes"}
我真正想要的是 playground 的输出#3。
看了转译后的代码后, 唯一的区别是反射元数据的代码:
//snip ...
__decorate([
exclude,
__metadata('design:type', String)
], MyClass.prototype, "testExclude", void 0);
__decorate([
enumerable(true),
__metadata('design:type', String)
], MyClass.prototype, "enumerated", null);
__decorate([
enumerable(false),
__metadata('design:type', String)
], MyClass.prototype, "nonEnumerated", null);
return MyClass;
}());
none 操场上 __metadata
行。
这里发生了什么?我怎样才能在我的项目中获得 playground 的第 3 名?
已修复(或者可能只是一种解决方法)。
请注意,在 playground 中,Reflect-metadata 不可用。 属性 装饰器可以 return 一个对象被赋值(或)到描述符来改变它的行为。在 angular 环境中,Reflect-metadata(特别是 Reflect.decorate()
)被用来装饰事物。
阅读 reflect-metadata doc, and this 后,显然无法更改 属性 装饰器上的 属性Descriptor,因为它与构造函数而不是原型相关联。解决方案(解决方法)是使用新描述符重新创建 属性。
function include(value: boolean) {
return function (target: any, propertyKey: string): any {
// Buffer the value
var _val = target[propertyKey];
// Delete property.
if (delete target[propertyKey]) {
// Create new property with getter and setter
Object.defineProperty(target, propertyKey, {
get: () => _val,
set: (newVal) => _val = newVal,
enumerable: value,
configurable: true
});
}
}
}
只需要工厂,所以我可以使用 @include(false)
而不是 @exclude
。
唯一的缺点是 属性 现在绑定到原型,因此正常的 JSON.stringify(instance)
不会序列化它。
关于这一点,我们可以进一步使通用装饰器在 属性 和方法中都可用,例如:
//method decorator
function excludeMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = false;
return descriptor;
};
//property decorator
function excludeProperty(target: any, propertyKey: string): any {
// Buffer the value
var _val = target[propertyKey];
// Delete property.
if (delete target[propertyKey]) {
// Create new property with getter and setter
Object.defineProperty(target, propertyKey, {
get: () => _val,
set: (newVal) => _val = newVal,
enumerable: false,
configurable: true
});
}
}
function exclude(...args : any[]) {
switch(args.length) {
case 2:
return excludeProperty.apply(this, args);
case 3:
if (typeof args[2] !== "number")
return excludeMethod.apply(this, args);
default:
throw new Error("Decorators are not valid here!");
}
}
所以现在我们可以这样使用它了:
class MyClass {
test: string = "test";
@exclude
testExclude: string = "should be excluded";
get enumerated(): string {
return "yes";
}
@exclude
get nonEnumerated(): string {
return "non enumerable"
}
constructor() {}
}
let x = new MyClass();
//to serialize, we have to whitelist the instance and its prototype prop keys
console.log(JSON.stringify(x, Object.keys(x).concat(Object.keys(MyClass.prototype))));
到目前为止,我还没有找到更简洁的方法。
我掉进了兔子洞...
所以出于某种原因,将白名单添加到 JSON.stringify 以某种方式使其无法递归序列化嵌套对象:
class a {
p1 = 1;
p2 = 2;
}
class b {
m1 = new a();
m2 = "test";
m3 = new Array<a>();
}
let i = new b();
i.m3.push(new a());
i.m3.push(new a());
JSON.stringify(i);
// properly gives
// {"m1":{"p1":1,"p2":2},"m2":"test","m3":[{"p1":1,"p2":2},{"p1":1,"p2":2}]}
JSON.stringify(i, Object.keys(i).concat(Object.keys(Object.getPrototypeOf(i))));
// nested class a doesn't get serialized
// {"m1":{},"m2":"test","m3":[{},{}]}
如果你像我一样想在 TS 中隐藏私有变量并给它一个只读外观,那么把它放在那里 属性:
将它声明为一个简单的对象成员,然后在构造函数中修改它的属性描述符:
//Don't do this
class a {
private _prop;
get prop() { return _prop; }
}
//do this instead
class a {
prop; //just define your public-facing property
constructor() {
let _prop; //internal variable here
Object.defineProperty(this, "prop", { //now we modify the existing prop,
get: () => _prop, //closure to outside variable
//(no set here, it's readonly after all)
enumerable: true, //make sure it's visible
configurable: false //close up access
});
}
}
现在我们可以简单地使用 JSON.stringify(instance)
。唯一的缺点是如果你有复杂的 getter/setter,请记住它在每个 instance/new.
使用此模式和上面的 @exclude 装饰器,几乎可以解决我的用例。希望这对某人有所帮助..