原型继承中引用类型属性共享是机制而非bug,需确保实例拥有独立副本:构造函数内初始化、类字段语法、getter返回新引用、借用构造函数继承可解决共享问题,验证方法包括修改后检查其他实例及比对原型属性内存地址。

原型继承中引用类型属性共享不是 bug,而是机制本身——所有实例通过原型链访问同一个数组或对象,改一个就全变。关键不是“避免原型”,而是确保每个实例拿到的是独立副本。
构造函数里初始化最直接
每次 new 都执行一次,天然隔离:
-
函数写法:在构造函数内部赋值,比如
this.items = []或this.config = { timeout: 5000 } -
类写法:用类字段语法(ES6+),如
items = [],它自动绑定到实例,不走 prototype - 注意别写成
Animal.prototype.items = [],这是共享的根源
getter 返回新引用适合只读场景
需要懒加载、或属性本就不该被修改时很合适:
- 每次访问都生成新对象/数组,彻底隔绝副作用,例如:
get defaults() { return { lang: 'zh', theme: 'light' }; } - 适合配置项、默认值、计算型数据,但不适合频繁读写的高频状态
- 不适用于需长期持有并多次修改的引用类型(比如缓存 map)
借用构造函数继承解决子类共享问题
当涉及父子类继承,且父类有引用属性时,仅靠原型链会出问题;借用构造函数能切断共享链:
- 在子类构造函数中调用
Parent.call(this, ...args),让父类逻辑在子类实例上执行 - 这样
this.colors等引用属性就落在子类实例自身,而非共用的父类原型 - 搭配
Object.create(Parent.prototype)设置子类原型,就能兼顾方法复用和属性隔离(即寄生组合继承)
快速验证是否已踩坑
两行代码就能暴露隐患:
- 改一个实例,再读另一个:
a.data.push(1); console.log(b.data)—— 若 b 也变了,说明共享 - 查内存地址:
Object.getPrototypeOf(a).data === Object.getPrototypeOf(b).data返回 true 就是同一份 - 团队审查时盯住
prototype.xxx = [] / {} / new Map()这类赋值语句











