原型式继承中引用类型属性被所有实例共享,本质是内存地址共用;应于构造函数、getter或类字段中初始化实例专属副本,避免原型上直接赋值{}、[]等可变对象。

原型式继承中引用类型属性被所有实例共享,本质是内存地址共用——不是“复制”,而是“共用同一份数据”。只要多个对象通过原型链访问同一个数组、对象或函数,它们就共享底层那块内存。
为什么引用类型会意外共享
原始值(如字符串、数字)赋值时会拷贝一份新值;而引用值(如 []、{}、new Map())赋值时只拷贝内存地址。当这个引用类型被挂到原型上(比如 Animal.prototype.items = []),所有实例读取 this.items 时,实际都指向原型上的同一个数组。
- 实例自身没有 items 属性 → 查找会落到原型上 → 操作的是同一份数据
- 一个实例执行 this.items.push('x') → 修改的是原型上的数组 → 其他实例读取时也看到新增项
- 即使你给某个实例单独赋值 obj.items = ['a'],也只是在该实例上新建了属性,不影响其他实例的访问路径
典型踩坑场景
这些写法表面无害,实则埋雷:
- 把空容器挂原型上:如 prototype.cache = {} 或 prototype.list = [],后续所有实例增删改都会互相干扰
- 事件监听器列表共用:多个实例调用 on('click', fn) 却往同一个 prototype.handlers 数组里 push,解绑时可能误删他人监听器
- 子类直接修改父类原型属性:看似只是“设置默认值”,但一旦可变,就会污染全部继承者
- Object.create() 复制对象时未处理引用字段:源对象含 skills: ['a', 'b'],所有 create 出的对象仍共享该数组
怎么验证是否已共享
快速检测两个实例是否共用同一引用:
- 用 Object.getPrototypeOf(instance1).hobbies === Object.getPrototypeOf(instance2).hobbies 判断是否指向同一数组
- 或直接修改一个实例的属性,再读另一个实例对应字段看是否变化
- 团队代码审查时,重点盯住 prototype.xxx = [] / {} / new Set() 这类赋值语句
安全初始化的正确做法
核心原则:让每个实例拥有自己的副本,而不是依赖原型上的“公共模板”。
- 在构造函数或 constructor 中初始化:this.hobbies = ['sleep', 'eat'] —— 每次 new 都新建数组
- 用 getter 返回新引用:get hobbies() { return ['sleep', 'eat']; } —— 每次访问都生成新数组(适合只读或懒加载)
- ES6 类字段语法(推荐):hobbies = ['sleep', 'eat'] —— 自动绑定到实例,天然隔离
- 防御性检查 + 初始化:方法内先判断 !this.cache && (this.cache = {}),避免重复创建又不破坏已有值










