Typescript:装饰师在有棱角的项目和Typescript游乐场上的行为不同



在angular 2.0.0-rc1中,当我发现Typescript的私有属性根本不是私有的,并且get-set属性不是通过json.stringify.输出时,我需要将一个对象序列化为json

所以我开始布置班级:

//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

在打字游戏场上,这给了

{"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"}

我真正想要的是操场上的输出#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;
}());

操场上的__metadata线都没有。

这里发生了什么事?我如何才能在我的项目中取得操场的第三名成绩?

修复了它(或者可能只是一个变通方法)。

请注意,在操场中,"反射"元数据不可用。属性装饰器可以将要分配(ORed)给描述符的对象返回,以更改其行为。在有角度的环境中,反射元数据(特别是Reflect.decorate())被用来装饰事物。

在阅读了反射元数据文档之后,显然无法更改属性装饰器上的PropertyDescriptor,因为它绑定到构造函数而不是原型。解决方案(解决方法)是使用新描述符重新创建属性。

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

只需要工厂,所以我可以使用CCD_ 3而不是CCD_。

唯一的缺点是属性现在绑定到原型,因此普通的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中隐藏私有变量,并给它一个只读的facade属性:,就把它放在那里吧

将其声明为简单对象成员,然后在构造函数中修改其propertyDescriptor,如下所示:

//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
}); 
}
}

现在我们可以简单地使用CCD_ 6。唯一的缺点是,如果您有复杂的getter/setter,请记住,它在每个实例/new中都会被调用。

有了这个模式和上面的@exclude装饰器,基本上解决了我的用例。希望这能帮助到别人。。

相关内容

  • 没有找到相关文章

最新更新