在 Javascript / TypeScript 中从父类调用被覆盖的方法时如何绑定子类“this”?

How do bind subclass 'this' when calling an overridden method from the parent class in Javascript / TypeScript?

提问人:Aniket Kumar 提问时间:11/14/2023 最后编辑:Aniket Kumar 更新时间:11/14/2023 访问量:31

问:

我正在尝试使用 HTML5 Canvas API 实现乒乓球游戏。为了保持井井有条,我创建了一个(基)类,并让每个 Canvas 对象都实现了这个通用 API,包括 .问题是传递给子类的子类没有任何子类的属性。如何解决此问题?DrawablePaddlethis

我已经提取了问题,这是相同代码的简化版本。

// File: parent.ts
export abstract class Parent {
  
    constructor(protected readonly parentProp: string) {
       if (autoSetup) this.setup();
    }

    setup(): void { /* no default setup required */ }
}
// File: child.ts
import { Parent } from './parent.ts';

export class Child extends Parent {
    
    constructor(prop1: string, public prop2: string) {
        super(prop1);
    }

    override setup(): void {
        console.log(this.prop2);  // undefined, why?
        
        // do other things with prop2
    }
}

现在,当 实例化时,类构造函数调用设置并将其传递给它自己的 .如何使它也包括子类属性?ChildParentthisthis

我觉得我错过了一些我不知道解决这个问题的关键技术。如果我不能解决这个问题,那么唯一的方法就是重新考虑整个代码并将设置从超类中提取出来,这将破坏基于 OO 的方法的目的。

提前感谢您的任何帮助!:)

打字稿 javascript 对象 这个

评论

3赞 jcalz 11/14/2023
您能否编辑此代码以消除使问题不那么明显的干扰:参数(就在这里);以及隐藏属性赋值位置的参数属性。您的代码等效于此版本,这是一个最小的可重现示例,您可以在其中确切地看到原因和尚不存在。您想以某种方式移动这些作业,但这是不允许的,因此问题将是要求替代方案。autoSetuptrueprop2prop3super
0赞 Pointy 11/14/2023
在父/子类关系中,父代码和子代码之间是完全相同的;只有一个对象。this
0赞 Aniket Kumar 11/14/2023
@jcalz 嘿!我已经编辑了代码以使问题更清晰。这有帮助吗?我面临的问题是为什么未定义?它应该初始化为构造函数传递的任何内容。是的,作为替代方案,我应该如何处理这个问题呢?this.prop2
0赞 jcalz 11/14/2023
我建议这个版本是为了使问题更清楚。你能用它来代替吗?如果不是,为什么不呢?
0赞 Aniket Kumar 11/16/2023
@jcalz当然!在你提供的代码版本中,你将如何实现我上面描述的内容?
1赞 Daniel A. White 11/22/2023
这是不应从构造函数调用“虚拟”方法的众多原因之一

答:

0赞 Michael T 11/22/2023 #1

不幸的是,您尝试做的事情是不可能的。类继承在 JS 中的工作方式是,在调用完成运行之前,派生类的对象不会初始化(另请参阅为什么在 super() 之前不允许这样做thissuper()

调用 时,调用类的构造函数,而构造函数又调用类的方法。正在运行时,仍未完成,这就是为什么 still 未在 的上下文中初始化的原因。由于此时仅在 的上下文中初始化,因此只能访问由 的构造函数初始化的字段(例如 )。这就是为什么你不能使它成为在调用时包含子类属性的原因。super()Parentsetup()Childsetup()super()thisChildthisParentParentthis.parentPropthissetup()

需要注意的另一点是 TypeScript 的参数属性是如何工作的。从本质上讲,它只是语法糖,相当于声明字段并在调用后立即初始化它们。这意味着您的代码等效于以下内容:super()

export abstract class Parent {
    protected readonly parentProp: string;
    constructor(parentProp: string) {
        this.parentProp = parentProp;
        if (autoSetup) this.setup();
    }

    setup(): void { /* no default setup required */ }
}

export class Child extends Parent {
    public prop2: string;
    constructor(prop1: string, prop2: string) {
        super(prop1);
        this.prop2 = prop2;
    }

    override setup(): void {
        console.log(this.prop2);
    }
}

正如你所看到的,何时被调用,还没有被定义,所以它是。setup()this.prop2undefined


我建议你在通话结束后从班级内部打电话,而不是上面的电话,就像这样:setup()Childsuper()

export abstract class Parent {
    constructor(protected readonly parentProp: string) { }

    abstract setup(): void
}

export class Child extends Parent {
    constructor(prop1: string, public prop2: string) {
        super(prop1);
        if (autoSetup) this.setup();
    }

    override setup(): void {
        console.log(this.prop2);
    }
}

new Child('prop1', 'prop2') // Prints "prop2"

这样,将在 的上下文中初始化后运行,因此将被正确定义。setup()thisChildthis.prop2

或者,如果您确实需要从 中调用,则可以将参数作为参数传递给构造函数,尽管在我看来这看起来更糟:setup()Parentprop2Parent

export abstract class Parent {
    constructor(protected readonly parentProp: string, prop2: string) {
       if (autoSetup) this.setup(prop2);
    }

    abstract setup(prop2: string): void
}

export class Child extends Parent {
    constructor(prop1: string, public prop2: string) {
        super(prop1, prop2);
        this.prop2 = prop2;
    }

    override setup(prop2: string): void {
        console.log(prop2);
    }
}

new Child('prop1', 'prop2') // Prints "prop2"