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继承构造函数的执行顺序导致的:
- 创建子类
Test实例时,首先调用super(id)执行父类ATest的构造函数。 - 父类构造函数中调用
this.init(),此时执行的是子类Test实现的init()方法——但子类构造函数还没执行super()之后的代码,子类的属性初始化逻辑未完成。 - 在
init()中给this.extFunc赋值2222看似生效,但JavaScript层面,子类的属性声明private extFunc!: number会在构造函数执行的最开始阶段(甚至在super()之前)被隐式设置为undefined。 - 父类构造函数执行完毕后,回到子类构造函数继续执行时,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
相关产品推荐
相关产品推荐

