JavaScript子类重定义公共类字段后父类构造函数赋值失效问题咨询
公共类字段继承赋值丢失问题解答
问题本质:不是bug,是ES规范定义的标准行为
子类实例化的固定执行流程如下:
- 触发子类constructor执行 → 调用super()进入父类构造逻辑 → 父类构造完全执行完成返回后,初始化子类声明的所有公共实例字段 → 继续执行子类constructor剩余逻辑
你遇到的赋值丢失原因非常明确:
父类构造运行时,子类的同名公共字段还未初始化,此时给this.publicFieldForThing赋值是写在实例上的临时值;等父类构造跑完,子类的公共字段初始化会直接在实例上创建同名属性,没有显式设置初始值的情况下默认赋值为undefined,直接覆盖了父类构造写入的值,所以第一个console.assert会触发失败。
而实例化完成后调用setThingField赋值时,子类的字段已经初始化完成,后续赋值不会被自动覆盖,所以第二个assert可以正常通过。
公共字段和getter行为差异的原因
两者的挂载位置和生效逻辑完全不同:
- 公共实例字段是直接绑定到每个实例对象自身的可写、可枚举、可配置属性,仅在实例初始化阶段执行一次赋值。
- getter/setter是定义在类的原型对象(prototype)上的访问器属性,不会写入实例自身,属性查询时是沿着原型链向上查找得到的,自然不会出现子类初始化覆盖实例属性的情况。
你提供的第二段测试代码的输出刚好可以验证该逻辑:
- 父类构造运行时,
prop能在this上找到,是因为查询时向上遍历到了Child.prototype上的getter - 父类构造运行时,
publicField在this上找不到,是因为子类的实例字段还没初始化到实例对象上 - super执行结束后,子类完成公共字段初始化,
publicField就出现在了实例自身上
解决方案(保留JSDoc类型提示的同时不丢失父类赋值)
方案1:子类不重复声明同名公共字段,仅用JSDoc补充类型
在子类定义上方通过JSDoc的@property标注字段类型,不需要显式声明公共字段,也就不会触发字段初始化的覆盖逻辑:
/** * @typedef {{colour: 'red'}} RedThing */ /** * @property {RedThing} publicFieldForThing */ class RedThingHolder extends ThingHolder { dropMyThing() { // 此处依然能拿到完整的JSDoc类型提示 } }
方案2:显式指定子类公共字段的初始值为父类对应值
如果必须在子类中声明字段(比如要适配特定的Lint规则、编译规则),可以显式给字段设置初始值:
class RedThingHolder extends ThingHolder { /**@type {RedThing}*/ publicFieldForThing = super.publicFieldForThing dropMyThing() { // 功能逻辑 } }
这样子类字段初始化时会读取父类构造赋的值作为初始值,不会出现覆盖为undefined的问题。
内容的提问来源于stack exchange,提问作者BlueWater86
相关产品推荐
相关产品推荐

