子类用super访问抽象类protected属性运行报错,TypeScript无类型错误
super.service访问父类protected实例属性会报错? 问题描述
在TypeScript中,子类尝试通过super访问父类的protected实例属性时,运行时会抛出TypeError: Cannot read properties of undefined (reading 'do'),但TypeScript类型检查不会报错。仅使用this访问时一切正常。
示例代码:
interface Service { do(): void; } abstract class Abstract { constructor(protected readonly service: Service) {} abstract execute(): void; } class Concrete extends Abstract { constructor(service: Service) { super(service); } execute(): void { super.service.do(); // 替换为this.service即可正常运行 } } class ServiceImpl implements Service { do(): void { console.log("i saved something"); } } const concrete = new Concrete(new ServiceImpl()); concrete.execute(); // 运行时抛出错误
运行时错误:
TypeError: Cannot read properties of undefined (reading 'do')
原因分析
JavaScript类属性的运行机制:
父类Abstract中的service是实例属性,在构造函数中声明后,实际是在实例化时直接赋值给this(即子类实例),并不会挂载到父类的原型对象上。
当使用super.service时,JavaScript会去父类的原型上查找service属性,但父类原型上并没有这个属性,因此返回undefined。TypeScript类型检查的偏差:
TypeScript的类型系统允许通过super访问父类的protected成员,这是从类型层面表明该成员来源于父类,但它没有区分原型属性和实例属性的运行时差异,导致类型检查不报错,但实际运行不符合预期。
解决方案
方案1:直接使用this.service
这是最简洁且符合运行逻辑的方式,service本身就是挂载在实例(this)上的属性,子类实例可以直接通过this访问父类的protected成员:
execute(): void { this.service.do(); // 正常运行 }
方案2:用JSDoc注释明确属性来源
如果想让其他开发者快速识别service来自父类,可以添加JSDoc注释说明,既不影响运行,又能提升可读性:
execute(): void { /** @type {Service} 继承自父类Abstract的protected属性 */ this.service.do(); }
方案3:将父类属性改为原型getter(可选)
如果一定要通过super访问,可以将父类的service改为原型上的getter方法,让super.service能正确指向父类原型的getter,最终返回实例上的属性:
abstract class Abstract { private readonly _service: Service; constructor(service: Service) { this._service = service; } // 定义原型getter protected get service(): Service { return this._service; } abstract execute(): void; } class Concrete extends Abstract { execute(): void { super.service.do(); // 现在可正常运行 } }
这种方式略显冗余,仅为满足super访问的需求,一般不推荐常规场景使用。
内容的提问来源于stack exchange,提问作者Davi Banfi Nogueira

