JavaScript继承中真正会被意外修改的是原型上的引用类型属性,因其被所有实例共享;解决方法是将引用类型初始化移至构造函数内,确保每个实例拥有独立副本。

JavaScript 继承中,真正会被“意外修改”的不是基本类型属性,而是原型上定义的引用类型(如数组、对象、正则、日期等)。因为这些值在原型上只存一份,所有实例共享同一内存地址。解决的关键是:让每个实例拥有自己独立的副本,而不是共用原型上的引用。
把引用类型初始化移到构造函数里
这是最直接有效的做法。只要每次创建实例时都新建引用,就能彻底切断共享链。
- ✅ 正确写法:在构造函数内用字面量或构造器初始化
比如this.items = []、this.config = { mode: 'light' }、this.cache = new Map() - ❌ 错误写法:在 prototype 上直接赋值引用类型
比如Parent.prototype.data = {}或Child.prototype.list = [],这会让所有实例读写同一个对象 - ⚠️ 注意隐式复用:
this.options = this.options || DEFAULT_OPTS若DEFAULT_OPTS是全局变量,仍会共享;应改为this.options = { ...DEFAULT_OPTS }或Object.assign({}, DEFAULT_OPTS)
用 Object.create 建立干净的原型链
避免使用 Child.prototype = new Parent() 这种方式——它会执行父构造函数,把父类的引用属性(如 this.colors = ['red'])直接挂到子类原型上,造成污染。
- ✅ 推荐:
Child.prototype = Object.create(Parent.prototype)
这样只继承方法,不带入父类实例属性 - ✅ 补充两步:
—Child.prototype.constructor = Child(修复 constructor 指向)
— 手动挂载子类特有方法,比如Child.prototype.sayHi = function() { ... }
验证是否真的隔离了引用
光看代码不够,要动手验证属性归属和内存地址。
- 检查是否为自有属性:
obj.hasOwnProperty('items')或Object.hasOwn(obj, 'items')返回true才说明在实例自身 - 比对引用:
p1.items === p2.items应该是false;若为true,说明仍共享 - 实测修改:
p1.items.push('x')后,检查p2.items是否不变 —— 这是最可靠的判断方式
ES6 class 也要注意同样的问题
class 只是语法糖,底层仍是原型机制。即使用了 extends 和 super(),如果在父类 prototype 上放了引用类型,照样会共享。
- ✅ 安全写法:
class Parent { constructor() { this.items = []; } }
子类中也应在constructor里调用super()并初始化自己的引用属性 - ❌ 危险写法:
class Parent { } Parent.prototype.items = [];
无论子类怎么写,所有实例的items都指向同一个数组











