Typescript decorator 和 Object.defineProperty 奇怪的行为



>我正在尝试实现一个覆盖属性 (1) 并定义隐藏属性 (2) 的装饰器。假设以下示例:

function f() {
return (target: any, key: string) => {
let pKey = '_' + key;
// 1. Define hidden property
Object.defineProperty(target, pKey, {
value: 0,
enumerable: false,
configurable: true,
writable: true
});
// 2. Override property get/set
return Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get: () => target[pKey],
set: (val) => {
target[pKey] = target[pKey] + 1;
}
});
};
}
class A {
@f()
propA = null;
propB = null;
}
let a = new A();
console.log(Object.keys(a), a.propA, a._propA, a);

哪些输出:

[ 'propB' ] 1 1 A { propB: null }

但是,我宁愿期待:

[ 'propA', 'propB' ] 1 1 A { propA: 1, propB: null }

由于enumerabletruepropA.

现在,如果我将getset替换为

get: function () {
return this[pKey]
},
set: function (val) {
this[pKey] = this[pKey] + 1;
}

输出现在为:

[ '_propA', 'propB' ] 1 1 A { _propA: 1, propB: null }

尽管enumerable明确设置为falsef中的_propA

所以,尽管这些行为很奇怪,但我想了解这里发生了什么,以及我将如何实现我想要得到的东西?

好吧,所以我花了一段时间,但我找到了解决方法。问题似乎是Object.defineProperty在装饰时无法正常工作。如果在运行时执行此操作,则事情会按预期进行。那么,如何在装饰器中定义属性,但在运行时?

这是诀窍:由于覆盖装饰器中的属性在装饰时有效(似乎只有可枚举的行为被破坏),您可以定义属性,但使用初始化函数代替gettersetter。该函数将在第一次分配(set)或访问(get)时运行。发生这种情况时,单词this引用对象的运行时实例,这意味着您可以在装饰时正确初始化要执行的操作。

这是解决方案:

function f() {
return (target: any, key: string) => {
let pKey = `_${key}`;
let init = function (isGet: boolean) {
return function (newVal?) {
/*
* This is called at runtime, so "this" is the instance.
*/
// Define hidden property
Object.defineProperty(this, pKey, {value: 0, enumerable: false, configurable: true, writable: true});
// Define public property
Object.defineProperty(this, key, {
get: () => {
return this[pKey];
},
set: (val) => {
this[pKey] = this[pKey] + 1;
},
enumerable: true,
configurable: true
});
// Perform original action
if (isGet) {
return this[key]; // get
} else {
this[key] = newVal; // set
}
};
};
// Override property to let init occur on first get/set
return Object.defineProperty(target, key, {
get: init(true),
set: init(false),
enumerable: true,
configurable: true
});
};
}

哪些输出:

[ 'propA', 'propB' ] 1 1 A { propA: [Getter/Setter], propB: null }

此解决方案支持默认值,因为它们是在初始化正确的 get/set 后分配的。

它还支持正确enumerable:将属性pKeyenumerable设置为true,输出将为:

[ '_propA', 'propA', 'propB' ] 1 1 A { _propA: 1, propA: [Getter/Setter], propB: null }

我知道它并不漂亮,但据我所知,它可以工作并且不会降低性能。

我检查了您的代码,发现它将定义该属性两次。我修改了你的代码。

class A {
@dec
public value: number = 5
}
function dec(target, key) {
function f(isGet: boolean) {
return function (newValue?: number) {
if (!Object.getOwnPropertyDescriptor(this, key)) {
let value: number;
const getter = function () {
return value
}
const setter = function (val) {
value = 2 * val
}
Object.defineProperty(this, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
})
}  
if (isGet) {
return this[key]
} else {
this[key] = newValue
}
}
}
Object.defineProperty(target, key, {
get: f(true),
set: f(false),
enumerable: false,
configurable: false
})
}
const a = new A()
console.log(Object.keys(a))

我们将进入控制台

["value"]

最新更新