原型继承中同名属性是静默遮蔽而非覆盖,实例属性优先于原型属性导致父类getter/setter失效;应统一用getter/setter定义父类属性、子类禁用同名this赋值、新增字段强制前缀、eslint/ast/运行时三重防护。

原型继承中同名属性被隐藏,不是运行时“覆盖”,而是编译期或查找期的名字遮蔽——它不报错、不警告,却让父类字段悄然失效。问题核心不在代码写错,而在开发者误以为“写了就用上了”,实际访问路径已断裂。
看清隐藏本质:不是消失,是访问断连
JavaScript 中,子类构造函数里直接写 this.id = 'xxx',和父类原型上定义的 get id() 或 this.id 初始化,属于不同作用域层级:
- 实例属性(this.id)优先于原型属性被读取,一旦存在,原型上的同名 getter/setter 或数据属性就完全不触发
- 这种遮蔽是单向且静默的:你改了子类的 this.id,父类的 id 还在原型上,但永远没机会执行
- 调试时打日志或 inspect 实例,看到的是子类自己的值,容易误判“父类逻辑没生效”,其实是根本没走到那里
从编码习惯切断误写链
避免靠记忆区分“该不该写 this.xxx”,把规则固化进写法:
- 父类中所有需被复用或受控的属性,统一通过 getter/setter 定义在原型上(如 get name() { return this._name; }),子类禁止直接赋值 this.name
- 子类若需扩展语义,重写 getter,内部用 super.name 获取原始值再加工,不新建同名字段
- 必须新增实例字段时,强制加前缀,如 this.childConfig、this.localState,和父类字段命名空间隔离
用工具提前暴露隐患
静默遮蔽最难防,靠人工 review 几乎无效,要靠机制卡点:
- ESLint 配置 no-shadow-restricted-names + 自定义规则,检测子类构造函数中对父类 getter 名称的直接赋值(如 this.id = ...)
- CI 流程加入 AST 扫描:识别继承链上同名属性声明,对非 getter/setter 场景标为高危,阻断合入
- 在父类基类中添加运行时防护:构造函数里检查 this.hasOwnProperty('id'),若为 true 则 warn 并打印调用栈,定位误写源头
重构存量代码的轻量过渡策略
已有项目出现大量误写,不宜全量重命名,可用渐进方式对齐:
- 先给父类同名字段加 @deprecated JSDoc,并在 getter 内部 warn,推动团队认知切换
- 子类中临时保留旧字段,但所有读写都走新命名(如 this.localId),旧字段仅用于兼容反序列化
- 统一提供迁移脚本:自动将 this.id = ... 替换为 this.localId = ...,并补上对应的 getter 封装











