TypeScript继承中如何避免父类构造调用抽象方法时this过早访问属性
问题本质
这是ECMAScript标准规定的类初始化顺序导致的必然问题:
- 子类构造函数执行时优先运行
super()调用父类构造逻辑 - 父类构造逻辑全部执行完成后,才会初始化子类声明的实例属性(包括构造参数中用
private/protected/public修饰隐式声明的属性) - 最后才会执行子类构造函数中
super()之后的代码
你在父类构造中调用抽象方法时,子类的extra1、extra2还没完成初始化,访问自然会报错。
可行方案
方案1:两步初始化+工厂方法(推荐)
构造函数本身仅适合做参数赋值这类无副作用的基础操作,不要在构造函数中执行业务逻辑。你可以把初始化逻辑抽离到单独的init方法中,由工厂方法统一管控实例初始化流程,既保留基类对初始化流程的控制权,也能避免this过早访问的问题:
abstract class BasicSystem { // 构造函数仅做参数存储 constructor(protected basic: any) {} // 初始化逻辑统一放在init中执行 init() { this.basicCall(); this.extraCall(); // 其他基类需要执行的初始化逻辑都放在这里 } private basicCall() { this.basic.doThings(); } protected abstract extraCall(): void; } class ExtraSystem extends BasicSystem { constructor(basic: any, private extra1: any, private extra2: any) { super(basic); } protected extraCall() { if (this.extra1.isGood) this.extra1.runSomething(); else if (this.extra2.isGood) this.extra2.runSomethingElse(); } } // 工厂方法统一创建实例,保证初始化流程正确 function createExtraSystem(basic: any, extra1: any, extra2: any): ExtraSystem { const instance = new ExtraSystem(basic, extra1, extra2); instance.init(); return instance; } // 使用方式 const system = createExtraSystem(basicImpl, extra1Impl, extra2Impl);
这个方案的优势是完全不破坏基类的抽象设计,基类依然可以自由编排初始化流程,子类不需要额外调整原有逻辑,只需要通过工厂方法创建实例即可。
方案2:透传依赖到父类存储
如果一定要在构造阶段完成所有初始化逻辑,可以把子类需要的依赖通过父类构造参数透传,父类提供受保护的访问入口供子类使用:
abstract class BasicSystem { constructor(private basic: any, protected childDeps: Record<string, any>) { this.basicCall(); this.extraCall(); } private basicCall() { this.basic.doThings(); } protected abstract extraCall(): void; } class ExtraSystem extends BasicSystem { constructor(basic: any, extra1: any, extra2: any) { // 把子类依赖提前传给父类存储 super(basic, { extra1, extra2 }); } protected extraCall() { // 从父类的childDeps中取依赖,此时已经完成赋值 const { extra1, extra2 } = this.childDeps; if (extra1.isGood) extra1.runSomething(); else if (extra2.isGood) extra2.runSomethingElse(); } }
这个方案适合依赖数量少、子类扩展场景不多的情况,缺点是父类需要感知子类的依赖传递,抽象性会稍弱。
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

