You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript抽象类中抽象方法初始化子类属性后为何变为undefined?

问题

测试TypeScript抽象类时遇到异常行为:定义抽象类ATest,构造函数直接初始化absCons属性,通过抽象方法init()初始化absFunc属性;子类Test继承ATest,构造函数中初始化extCons,实现的init()方法中初始化extFunc。测试发现init()方法内extFunc显示为2222,但调用check()方法时extFunc却变为undefined。

相关代码:

abstract class ATest {
  protected readonly absCons: number;
  protected readonly absFunc: number;

  constructor(id: number) {
    this.absCons = id;
    this.absFunc = this.init();
  }

  protected abstract init(): number;
}

class Test extends ATest {
  private readonly extCons: number;
  private extFunc!: number;  // 问题属性
  
  constructor(id: number) {
    super(id);
    this.extCons = 7777;
  }

  protected init(): number {
    console.log('In init()');
    this.extFunc = 2222;
    const tmp = 4444;
    console.log('absCons =', this.absCons);
    console.log('absFunc =', tmp, '(tmp)');
    console.log('extCons =', this.extCons);
    console.log('extFunc =', this.extFunc, '<<<<<<<<<<<<<<<');
    console.log('Leaving init()\n');
    return tmp;
  }

  public check(): void {
    console.log('In check()');
    console.log('absCons =', this.absCons);
    console.log('absFunc =', this.absFunc);
    console.log('extCons =', this.extCons);
    console.log('extFunc =', this.extFunc, '<<<<<<<<<<<<<<<');
    console.log('Leaving check()\n');
  }
}

const t = new Test(1111);
t.check();

输出结果:

In init()
absCons = 1111
absFunc = 4444 (tmp)
extCons = undefined
extFunc = 2222 <<<<<<<<<<<<<<<<
Leaving init()

In check()
absCons = 1111
absFunc = 4444
extCons = 7777
extFunc = undefined <<<<<<<<<<<<<<<<
Leaving check()

原因分析

这是TypeScript继承构造函数的执行顺序导致的:

  1. 创建子类Test实例时,首先调用super(id)执行父类ATest的构造函数。
  2. 父类构造函数中调用this.init(),此时执行的是子类Test实现的init()方法——但子类构造函数还没执行super()之后的代码,子类的属性初始化逻辑未完成。
  3. 在init()中给this.extFunc赋值2222看似生效,但JavaScript层面,子类的属性声明private extFunc!: number会在构造函数执行的最开始阶段(甚至在super()之前)被隐式设置为undefined。
  4. 父类构造函数执行完毕后,回到子类构造函数继续执行时,JavaScript会再次对extFunc执行初始化操作(重置为undefined),覆盖了init()中赋予的2222值,因此check()中看到的是undefined。

能否在抽象方法中初始化子类属性?

可以,但必须避开构造函数执行顺序的坑:

  • 禁止在父类构造函数中调用子类的抽象方法(或任何子类重写的方法),此时子类构造逻辑未完成,属性可能被后续初始化覆盖。
  • 正确做法:父类提供初始化入口方法,在子类构造函数的super()之后主动调用,而非在父类构造函数中自动调用。

修改后的示例代码:

abstract class ATest {
  protected readonly absCons: number;
  protected readonly absFunc: number;

  constructor(id: number) {
    this.absCons = id;
    this.absFunc = 0;
  }

  protected abstract init(): number;

  // 提供初始化入口
  public initialize(): void {
    this.absFunc = this.init();
  }
}

class Test extends ATest {
  private readonly extCons: number;
  private extFunc!: number;
  
  constructor(id: number) {
    super(id);
    this.extCons = 7777;
    this.initialize(); // 子类构造函数中主动调用初始化
  }

  protected init(): number {
    console.log('In init()');
    this.extFunc = 2222;
    const tmp = 4444;
    console.log('absCons =', this.absCons);
    console.log('absFunc =', tmp, '(tmp)');
    console.log('extCons =', this.extCons);
    console.log('extFunc =', this.extFunc, '<<<<<<<<<<<<<<<');
    console.log('Leaving init()\n');
    return tmp;
  }

  public check(): void {
    console.log('In check()');
    console.log('absCons =', this.absCons);
    console.log('absFunc =', this.absFunc);
    console.log('extCons =', this.extCons);
    console.log('extFunc =', this.extFunc, '<<<<<<<<<<<<<<<');
    console.log('Leaving check()\n');
  }
}

const t = new Test(1111);
t.check();

此时输出恢复正常:

In init()
absCons = 1111
absFunc = 4444 (tmp)
extCons = 7777
extFunc = 2222 <<<<<<<<<<<<<<<<
Leaving init()

In check()
absCons = 1111
absFunc = 4444
extCons = 7777
extFunc = 2222 <<<<<<<<<<<<<<<<
Leaving check()

内容的提问来源于stack exchange,提问作者ENIAC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 18:43:10