原型上共享引用类型会导致所有实例共用同一内存,修改一个即影响全部;因引用值赋值只复制地址,而原始值会复制新值,故应于构造函数、类字段或getter中为每个实例初始化独立副本。

原型继承中,数组和对象这类引用类型一旦挂在原型上,所有实例就会共用同一份内存——改一个,全跟着变。这不是设计缺陷,而是原型委托机制的自然结果:实例自身没该属性,就去原型上找;找到的又是同一个数组或对象,操作自然互相影响。
为什么数组和对象特别容易踩坑
原始值(如数字、字符串)赋值时会复制一份新值;而引用值([]、{}、new Map())赋值时只复制地址。当写成 Animal.prototype.items = [],等于让所有实例都指向内存里那一个空数组。
- 实例调用
this.items.push('a')→ 修改的是原型上的数组 - 另一个实例读
this.items→ 看到的已是['a'] - 即使给某个实例单独赋值
obj.items = ['x'],也只是在它自己身上新建了属性,不改变其他实例的查找路径
典型危险写法(务必避开)
这些代码看似简洁,实则埋下共享隐患:
-
MyClass.prototype.cache = {}—— 多个实例缓存互相覆盖 -
Component.prototype.handlers = []—— 绑定事件时往同一个数组里push,解绑可能误删他人监听器 -
Parent.prototype.config = { timeout: 5000 }—— 子类修改 config 会污染全部继承者 -
Object.create({ list: [] })—— 两个 create 出的对象仍共享list数组
安全初始化的三种可靠方式
核心原则:让每个实例拥有自己的副本,而不是依赖原型上的“公共模板”。
-
构造函数内初始化:每次
new都新建引用function Person() { this.hobbies = ['read', 'walk']; } -
ES6 类字段语法(推荐):自动绑定到实例,天然隔离
class User { hobbies = ['read', 'walk']; } -
getter 返回新引用:适合只读或需懒加载的场景
get settings() { return { theme: 'light', lang: 'zh' }; }
快速验证是否已共享
不用跑完整测试,两行代码就能判断:
- 检查两个实例的属性是否指向同一引用:
Object.getPrototypeOf(a).data === Object.getPrototypeOf(b).data - 直接改一个,再读另一个:
a.data.name = 'test'; console.log(b.data.name); // 若输出 'test' 就已共享











