JavaScript继承本质是复用而非模仿类,通过__proto__链实现属性方法访问;原型链继承因共享引用类型属性存在缺陷,组合继承则用call/apply隔离实例属性、Object.create复用原型方法,兼顾安全与兼容。

继承的本质是复用,不是模仿类
JavaScript 中没有真正的“类继承”,所谓继承,其实是让一个对象能访问另一个对象的属性和方法。原型链继承靠的是 __proto__ 链查找机制:当访问一个对象上不存在的属性时,引擎会顺着 __proto__ 一层层向上找,直到找到或到达 null。这是 JS 原生支持的机制,但直接用它实现“继承”有明显缺陷。
原型链继承的问题:共享引用类型属性
如果父构造函数中有引用类型属性(比如数组、对象),所有子实例都会共享它:
function Parent() {
this.colors = ['red', 'blue'];
}
Parent.prototype.sayHi = function() { console.log('hi'); };
function Child() {}
Child.prototype = new Parent(); // 关键问题在这里
const c1 = new Child();
const c2 = new Child();
c1.colors.push('green');
console.log(c2.colors); // ['red', 'blue', 'green'] ← 意外被修改了!
原因在于 new Parent() 创建的是一个真实实例,它的 colors 是挂在实例上的,而这个实例成了 Child 的 prototype —— 所有 Child 实例都通过原型链共享它。
立即学习“Java免费学习笔记(深入)”;
组合继承:构造函数 + 原型链,各干各的事
组合继承把“实例属性初始化”和“方法复用”分开处理:
- 在子构造函数中用 call/apply 调用父构造函数,确保每个实例都有独立的属性副本;
- 再把父类的原型方法赋给子类原型,实现方法复用。
function Parent(name) {
this.name = name;
this.colors = ['red'];
}
Parent.prototype.sayHi = function() { console.log('hi from ' + this.name); };
function Child(name, age) {
Parent.call(this, name); // ✅ 每个实例独享 name/colors
this.age = age;
}
Child.prototype = Object.create(Parent.prototype); // ✅ 复用方法,不执行父构造函数
Child.prototype.constructor = Child; // ✅ 修复 constructor 指向
const c1 = new Child('Alice', 10);
const c2 = new Child('Bob', 12);
c1.colors.push('blue');
console.log(c2.colors); // ['red'] ← 正常,不共享
为什么它更常用:安全、清晰、兼容性好
组合继承不依赖 ES6+ 语法,所有浏览器都支持;逻辑明确:构造函数负责“数据隔离”,原型负责“行为复用”。虽然它会在子类原型上多调用一次父构造函数(不影响实例),但相比原型链继承的数据污染风险,这点小代价完全值得。现代开发中虽常用 class extends,但其底层正是组合继承的语法糖。











