原型链继承天然导致引用类型状态共享,因子类原型是父类实例,其引用属性被所有子实例共用;应将引用初始化移至构造函数或用object.create避免,只读配置可安全共享。

原型链继承本身不直接“实现”状态共享,而是天然导致引用类型状态被共享——这不是设计目标,而是机制副产物。关键在于:当子类的 prototype 被设为父类的一个实例时,这个实例中的属性(尤其是数组、对象、函数等引用类型)会成为所有子类实例共同访问的同一份内存。
为什么状态会共享?
因为子类原型是父类的一个具体实例,而不是一个“模板”或“蓝图”。例如:
function Animal() {
this.habits = { sleep: true, eat: 'meat' };
this.tags = ['mammal'];
}
function Dog() {}
Dog.prototype = new Animal(); // ← 这里创建了一个 Animal 实例,赋给 Dog.prototype
Dog.prototype.constructor = Dog;
此时 Dog.prototype.habits 和 Dog.prototype.tags 是真实存在的引用值。所有 new Dog() 创建的实例,在查找 habits 或 tags 时,都会落到这个唯一的原型对象上。
共享状态的典型表现
- 修改
dog1.tags.push('pet'),dog2.tags也会多出'pet' - 赋值
dog1.habits.sleep = false,dog2.habits.sleep同样变为false(因为habits是同一个对象) - 但
dog1.name = 'Leo'不会影响dog2——这是实例自身属性,不走原型链
如何避免意外共享?
真正需要的是隔离实例状态,而非追求共享。常用做法:
-
把引用类型初始化移到构造函数中:在
Animal构造函数里用this.habits = {...},而不是在原型上定义;这样每次new Animal()都生成新对象 -
用
Object.create(Parent.prototype)替代new Parent():它只建立原型链接,不执行父构造函数,也就不会把父实例的引用属性挂到子原型上 -
组合使用 call/apply 在子构造函数中调用父构造函数:确保每个实例都有独立的属性副本,例如
Animal.call(this)
什么时候适合主动利用共享?
仅限于只读的、不可变的配置数据,比如:
Animal.prototype.speciesList = Object.freeze(['lion', 'tiger', 'bear']);
Animal.prototype.maxAge = 25; // 基本类型,赋值不共享
这类值不会被实例修改,又可被所有后代复用,既安全又节省内存。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











