我正在研究 Typescript 装饰器,当它们与类继承一起使用时,它们的行为似乎与我预期的完全不同。
假设我有以下代码:
class A {
@f()
propA;
}
class B extends A {
@f()
propB;
}
class C extends A {
@f()
propC;
}
function f() {
return (target, key) => {
if (!target.test) target.test = [];
target.test.push(key);
};
}
let b = new B();
let c = new C();
console.log(b['test'], c['test']);
哪些输出:
[ 'propA', 'propB', 'propC' ] [ 'propA', 'propB', 'propC' ]
虽然我希望这样:
[ 'propA', 'propB' ] [ 'propA', 'propC' ]
因此,target.test
似乎在 A、B 和 C 之间共享。我对这里发生的事情的理解如下:
- 由于 B 扩展了 A,
new B()
首先触发了 A 的实例化,这触发了对 A 的f
求值。由于target.test
未定义,已初始化。 f
然后为 B 求值,因为它扩展了 A,所以首先实例化 A。因此,此时,target.test
(target
是 B)引用为 A 定义的test
。因此,我们推送propB
在里面。在这一点上,事情按预期进行。- 与步骤 2 相同,但对于 C。这一次,当 C 评估装饰器时,我希望它有一个新对象用于
test
,不同于为 B 定义的对象。但是日志事实证明我错了。
任何人都可以向我解释为什么会发生这种情况 (1) 以及我将如何实现 f
以便 A 和 B 具有单独的 test
属性?
我猜你会称之为“特定于实例”的装饰器?
最佳答案
好吧,在花了几个小时玩弄和搜索网络后,我得到了一个工作版本。我不明白为什么这行得通,所以请原谅缺乏解释。
关键是使用 Object.getOwnPropertyDescriptor(target, 'test') == null
而不是 !target.test
来检查是否存在 test
属性。
如果您使用:
function f() {
return (target, key) => {
if (Object.getOwnPropertyDescriptor(target, 'test') == null) target.test = [];
target.test.push(key);
};
}
控制台会显示:
[ 'propB' ] [ 'propC' ]
这几乎就是我想要的。现在,数组特定于每个实例。但这意味着数组中缺少 'propA'
,因为它是在 A 中定义的。因此我们需要访问父目标并从那里获取属性。我花了一些时间才弄清楚,但您可以使用 Object.getPrototypeOf(target)
获取它。
最终的解决方案是:
function f() {
return (target, key) => {
if (Object.getOwnPropertyDescriptor(target, 'test') == null) target.test = [];
target.test.push(key);
/*
* Since target is now specific to, append properties defined in parent.
*/
let parentTarget = Object.getPrototypeOf(target);
let parentData = parentTarget.test;
if (parentData) {
parentData.forEach(val => {
if (target.test.find(v => v == val) == null) target.test.push(val);
});
}
};
}
哪些输出
[ 'propB', 'propA' ] [ 'propC', 'propA' ]
可能有人理解为什么这有效,但上面没有启发我。
关于具有继承性的 Typescript 装饰器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43912168/