TypeScript类私有属性在受保护函数中未定义的问题与最佳实践咨询
TypeScript类继承中构造函数调用子类方法导致属性未初始化的解决方案
你猜的没错,问题确实出在类的初始化顺序上:
- 创建
AppRoute实例时,首先执行父类RouteBase的构造函数 - 父类构造里调用了
init(),进而触发registerRoute()——但此时子类AppRoute的apiRoute属性还没完成初始化(子类实例属性是在父类构造执行完毕后才赋值的) - 因此
this.apiRoute为undefined,自然无法读取router属性
下面是几种可行的最佳实践:
方案1:分离初始化逻辑,子类主动触发
修改父类RouteBase,移除构造函数中的自动初始化逻辑,让子类在super()之后手动调用init(),确保子类属性已经初始化完成:
// route.base.ts export abstract class RouteBase { public router = Router() constructor() {} // 不再自动执行初始化 public init(): void { this.registerRoute() } protected abstract registerRoute(): void }
子类构造函数中,在super()后调用init():
// app.routing.ts export class AppRoute extends RouteBase { private apiRoute = new ApiRoute() constructor() { super() // 此时apiRoute已完成初始化,再执行路由注册 this.init() } protected registerRoute(): void { this.router.use('/api', this.apiRoute.router) } }
// api.routing.ts export class ApiRoute extends RouteBase { constructor() { super() this.init() } protected registerRoute(): void { this.router.get('/test', (req, res, next) => res.send('api test.')) } }
使用时正常创建实例即可:
const appRoute = new AppRoute()
方案2:用静态工厂方法创建实例
如果不想手动调用init(),可以给父类添加静态工厂方法,确保实例属性初始化完成后再执行注册逻辑:
// route.base.ts export abstract class RouteBase { public router = Router() protected constructor() {} // 保护构造,强制通过静态方法创建实例 public init(): void { this.registerRoute() } protected abstract registerRoute(): void // 静态工厂:先创建实例(完成属性初始化),再执行初始化 public static create<T extends RouteBase>(this: new () => T): T { const instance = new this() instance.init() return instance } }
子类无需修改构造函数,直接通过静态方法创建实例:
const appRoute = AppRoute.create() const apiRoute = ApiRoute.create()
核心原则
永远不要在父类构造函数中调用会被子类重写的方法——这是面向对象编程的常见陷阱,此时子类的实例成员还未完成初始化,极易引发未定义错误。将初始化逻辑延迟到子类构造之后,或通过工厂方法确保实例完全创建后再执行,是更安全的做法。
内容的提问来源于stack exchange,提问作者Andoma Chen
相关产品推荐
相关产品推荐

