TypeScript: 在派生类的构造函数中访问'this'之前必须先调用'super'。

32

我之前看到过类似的问题,但我认为我的问题更关注这种架构方法。
在TypeScript中,在调用super之前不能使用this关键字(在从另一个类继承的类上)
但是如果您需要像下面的示例一样执行某些操作怎么办?
(仅为澄清:我正在为UI库创建组件生命周期,因此感觉确实需要像这样做某些事情,而且我似乎想不出其他解决方法)。

代码

我想要做的就是这样:

class Person 
{
    public firstName: string;

    constructor()
    {
        this.scream();
    }

    protected scream(): void
    {
        console.log(this.firstName);
    }
}

class Employee extends Person
{
    public lastName: string;

    constructor(firstName: string, lastName: string)
    {
        this.lastName = lastName;
        super(firstName);
    }

    protected scream(): void
    {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

问题

父类"Person"的构造函数调用了一个受保护的方法。
子类"Employee"在覆盖这个受保护的方法时,想要使用自己的参数 (this.lastName)。
但是上面的代码会抛出错误(至少在Webstorm中):
“衍生类的构造函数中访问'this'之前必须先调用'super'。”

可能解决方案

A) 将this.lastName = lastNamesuper调用交换

class Employee extends Person
{
    ...

    constructor(firstName: string, lastName: string)
    {
        super(firstName);
        this.lastName = lastName;
    }

    ...
}

这里的问题是在 'Employee' 类的 scream() 方法中,this.lastName 将会是 undefined

B)
使用 setTimeout(callback, 0)。这样 this.scream() 方法将会在稍后被调用。

class Person 
{
    ...

    constructor()
    {
        setTimeout(() => this.scream(), 0);
    }

    ...
}

但这对我来说感觉就像是一个非常丑陋的黑客手段。

C)
不要从 Person 类内部调用 this.scream(),而应该从消费者中调用它。

const employee: Employee = new Employee();
employee.scream();

=> 但显然这并不总是您想要的。

问题

  • 我在这里做了一个愚蠢的事情吗?
  • 有更好的方法来安排我的代码,使我不需要这样做吗?
  • 是否有一些方法可以解决这个错误?

5
构造函数应该只完成对象的构造工作,即为字段赋予正确的值。因此,最好的方法是不在构造函数中调用scream()。您可以随时从外部调用它,这样就永远不会有这个问题了。让构造函数执行逻辑代码从来都不是一个好主意。 - iberbeu
1
顺便说一下,选项B)真的是你绝对不应该做的事情。而选项C)应该是你始终想要的。 - iberbeu
我同意,但是在创建生命周期时,有时候没有其他选择,不是吗? - dotdotcommadot
@iberbeu 如果基类需要派生类中重写方法的结果来正确构造基类,该怎么办? - Decade Moon
@DecadeMoon 如果基类需要一个被覆盖的方法,那么你就是让基类依赖于它的子类,这是不好的。无论如何,请检查我发布的解决方案。在这种情况下,这对我来说是最好的方法。 - iberbeu
3个回答

10

除了@iberbeu和@Nypan提供的解决方案之外,我最终想出的另一个解决方案是在调用之前添加一个中间initProps()方法:

class Person 
{
    public firstName: string;

    constructor(firstName: string, props?: any)
    {
        this.firstName = firstName;
        this.initProps(props);
        this.scream();
    }

    protected initProps(props: any): void
    {
    }

    protected scream(): void
    {
        console.log(this.firstName);
    }
}

class Employee extends Person
{
    public lastName: string;

    constructor(firstName: string, lastName: string)
    {
        super(firstName, {lastName});
    }

    protected initProps(props: any): void
    {
        this.lastName = props.lastName;
    }

    protected scream(): void
    {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

尽管我认为两者都有强有力的观点,我实际上应该使用工厂模式。

1
谢谢!这正是我需要的设计模式。我的设置是: ``` - jladan

6

我这里做的事情很蠢吗?

是的,你是的。正如iberbeu在他的评论中所说,构造函数不应该执行与对象构造无关的任何操作。这是一种不良实践,可能会导致各种意想不到的行为。

有更好的方式来安排我的代码,这样我就不需要这样做了吗?

使用你在C选项中提供的解决方案是正确的方法。

有什么办法可以解决这个错误吗?

这取决于你实际想要做什么。你自己在C选项中所示的是正常的做法。如果你遇到的问题与实例化复杂对象有关,你可能需要考虑生成器/工厂模式。但如果你真的希望构造函数执行某些操作,那么你只是错了;构造函数不是用来执行操作的,它们只是用来构造对象而已。


3

正如我在我的评论中所写,以及@Nypan在他的答案中所说,你应该避免这样做。无论如何,一种可能是在Child类中覆盖方法scream并调用一个新方法。请看下面的代码:

class Person 
{
    public firstName: string;

    constructor() {
        this.scream();
    }

    protected scream(): void {
        console.log(this.firstName);
    }
}

class Employee extends Person
{
    public lastName: string;

    constructor(firstName: string, lastName: string)
    {
        super(firstName);
        this.lastName = lastName;
        this.screamOVerriden();
    }

    protected scream(): void {
        // do nothing
    }

    protected screamOverriden(): void {
        console.log(this.firstName + ' ' + this.lastName);
    }

}

我仍然不建议这样做,但如果你说你确实需要它,并且你不在乎正确地完成它,那么这可能是一个解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接