gpt4 book ai didi

typescript - 为什么基类上的 TypeScript 装饰器会导致扩展相互影响?

转载 作者:行者123 更新时间:2023-12-01 22:10:44 25 4
gpt4 key购买 nike

假设您有一个装饰器来跟踪它装饰了哪些字段:

interface FieldTracker {
fields: string[];
}

const decorator = <T extends FieldTracker>(target: T, fieldName: string) => {
target.fields = target.fields || [];
target.fields.push(fieldName);
};

然后假设您创建了一个使用该装饰器的抽象 Base 类:

abstract class Base implements FieldTracker {
fields: string[];

@decorator
a: string = 'a';
}

然后您创建两个扩展 Base 类的类。

class FirstClass extends Base {
@decorator
b: string = 'b';
}

class SecondClass extends Base {
@decorator
c: string = 'c';
}

在实例化 SecondClass 时,它的 fields 将包含在 FirstClass 中修饰的字段:

const secondInstance = new SecondClass();

expect(secondInstance.fields).toEqual(['a', 'c']);

这导致:

Expected value to equal:
["a", "c"]
Received:
["a", "b", "c"]

一些观察

  • 如果我将参数记录到 decorator,我得到:

    1. target: Base {}, fieldName: 'a'
    2. target: FirstClass {}, fieldName: 'b'
    3. 目标:SecondClass {},fieldName:'c'
  • 请注意,Base抽象的,无法实例化。 target 如何成为它的一个实例?

  • 请注意,我什至从未实例化过 FirstClasstarget 如何成为它的一个实例?

  • 如果我不在 Base 上使用 decorator,则不会发生这种情况。这意味着 fieldsBase.prototype 上,这似乎不应该存在。

最佳答案

这里有点困惑。

  • Base 类的抽象 性质意味着,如果您尝试直接构造 Base< 的实例,编译器将会对您大喊大叫 与它。它仍然具有类构造器的所有功能,包括拥有一个原型(prototype)。如果您在 the TypeScript Playground 检查您的代码发出的 JavaScript,您可以看到这一点。 .

  • 装饰器作用于每个类构造函数的prototype(所以当你装饰Base时,它正在修改Base.prototype ).它不(直接)作用于该类的任何实例。对于您装饰的每个类,装饰器只被调用一次。

  • 子类的原型(prototype) inherits from父类(super class)的 prototype。这样,子类实例的原型(prototype)链包括子类构造函数的 prototype 以及父类(super class)构造函数的 prototype

  • 如果您将一个数组赋值给一个变量,您并不是在复制数组的内容;只有一个数组对象,您从一个变量对其所做的任何更改都将在另一个变量中可见。

话虽如此,让我们检查一下您的装饰器:

const decorator = <T extends FieldTracker>(target: T, fieldName: string) => {
target.fields = target.fields || []; // hmm
target.fields.push(fieldName);
};

在标记为 //hmm 的行中,您正在检查传入的 prototype 对象的 fields 属性。对于 Base.prototype,这将不存在并将被初始化为一个新的空数组。对于 FirstClass.prototype,这不会被直接找到,但由于 FirstClass.prototype 继承自 Base.prototype,它会在那里找到。通过将 FirstClass.prototype.fields 设置为 Base.prototype.fields,您将属性直接添加到 FirstClass.prototype,但值是与 Base.prototype 相同的数组对象。当您将值推送到 FirstClass.prototype.fields 时,您也会在 Base.prototype.fields 上看到更改。对于 SecondClass.prototype.fields 也是类似的。

这意味着你得到了不良行为:

console.log(Base.prototype.fields);  // Array [ "a", "b", "c" ]
console.log(FirstClass.prototype.fields); // Array [ "a", "b", "c" ]
console.log(SecondClass.prototype.fields); // Array [ "a", "b", "c" ]

解决这个问题很简单;不要复制数组引用,而是将其内容复制到一个新数组。最简单的方法是使用原始数组的 slice() method :

const decorator = <T extends FieldTracker>(target: T, fieldName: string) => {
target.fields = (target.fields || []).slice(); // all better
target.fields.push(fieldName);
};

现在,如果您运行上面的代码,测试应该会通过。具体来说,您应该看到:

console.log(Base.prototype.fields);  // Array [ "a" ]
console.log(FirstClass.prototype.fields); // Array [ "a", "b" ]
console.log(SecondClass.prototype.fields); // Array [ "a", "c" ]

希望对您有所帮助;祝你好运!

关于typescript - 为什么基类上的 TypeScript 装饰器会导致扩展相互影响?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48122319/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com