JS子类声明字段后用this访问报错,如何正确使用子类字段?
在JavaScript子类中声明字段后,使用this访问时出现错误,尝试箭头函数也无法解决。
简化示例代码
class Shape {} class Polygon extends Shape { name; constructor() { this.name = 'Polygon'; } cout() { console.log(this.name); } } const poly1 = new Polygon(); poly1.cout(); // 返回错误 // 预期输出: "Polygon"
实际业务代码
class TankScene extends Scene { tank1; collidables; buildScene() { super.buildScene(); this.collidables = []; this.tank1 = new ControllableTank(SpriteTypes.blueTank); this.floor = new GameObject("Floor", ModelTypes.plane, SpriteTypes.woodenFloor); this.tank1.tankBody.uniformSetScale(5); this.floor.uniformSetScale(10); this.collidables.push(this.tank1.tankBody); this.addChild(this.tank1); this.addChild(this.floor); this.tank1.setCollidables(this.collidables); } doUpdate() { super.doUpdate(); console.log(this.tank1); } }
不声明tank1和collidables字段时,日志能输出预期值,但声明后就会出错,请问问题出在哪里?
简化示例的核心问题
你的简化代码直接报错的原因很明确:JavaScript子类的构造函数中,必须先调用super()才能访问this。不管父类有没有显式构造函数,只要子类定义了构造函数,就必须先执行super()完成父类初始化,才能操作子类的this。
修改后的简化代码:
class Shape {} class Polygon extends Shape { name; constructor() { super(); // 必须先调用super() this.name = 'Polygon'; } cout() { console.log(this.name); } } const poly1 = new Polygon(); poly1.cout(); // 正常输出: "Polygon"
实际业务代码的问题
结合你描述的“不声明字段正常,声明后出错”的现象,大概率是以下两种情况之一:
情况1:父类构造函数提前调用了子类方法
如果父类Scene的构造函数中调用了buildScene或doUpdate,子类字段的初始化时机就会引发问题:
JavaScript类字段的执行顺序是:先调用super()(执行父类构造)→ 初始化子类字段(赋值undefined)→ 执行子类构造的自定义代码。
如果父类构造里调用了buildScene,此时子类字段已经被初始化为undefined,但你在buildScene里的赋值逻辑还没执行,就会导致后续访问this.tank1时拿到undefined。
情况2:方法的this指向丢失
如果doUpdate或buildScene是被当作回调函数(比如定时器、事件监听)调用的,this会脱离实例上下文,变成window(非严格模式)或undefined(严格模式):
不声明字段时,可能父类的绑定逻辑让this保持正确;但声明字段后,类字段是实例的自有属性,this指向错误时访问this.tank1就会报错或返回undefined。
解决方法
- 把方法改成箭头函数,强制
this始终指向实例:
class TankScene extends Scene { tank1; collidables; buildScene = () => { // 原代码逻辑不变 } doUpdate = () => { super.doUpdate(); console.log(this.tank1); } }
- 或者在子类构造函数中手动绑定
this:
class TankScene extends Scene { tank1; collidables; constructor() { super(); this.buildScene = this.buildScene.bind(this); this.doUpdate = this.doUpdate.bind(this); } // 原方法逻辑不变 }
额外排查点
- 检查父类
Scene是否有同名的tank1或collidables字段,子类声明的字段会覆盖父类同名属性,可能引发逻辑冲突。 - 确认代码运行环境支持ES2022+的类字段语法,旧环境需要用Babel转译,否则字段声明会触发语法错误中断代码执行。
内容的提问来源于stack exchange,提问作者The Good Goomba

