✅ 1. 原型链继承(Prototype Chaining)
原理:
将子类的原型设置为父类的一个实例。这样子类的实例就可以访问父类的属性和方法。
优点:
- 实现简单。
- 可以继承父类原型上的方法。
缺点:
- 父类的引用类型属性会被所有子类实例共享(共享引用问题)。
- 创建子类实例时,无法向父类构造函数传参。
示例代码:
javascript
function Parent() {
this.colors = ["red", "blue", "green"];
}
Parent.prototype.sayHello = function () {
console.log("Hello from Parent");
};
function Child() {}
Child.prototype = new Parent(); // 核心:将子类的原型设为父类实例
let child1 = new Child();
child1.colors.push("black");
console.log(child1.colors); // ["red", "blue", "green", "black"]
let child2 = new Child();
console.log(child2.colors); // ["red", "blue", "green", "black"] (共享引用)
✅ 2. 构造函数继承(Constructor Stealing / Classic Inheritance)
原理:
在子类构造函数中调用父类构造函数,使用 call()
或 apply()
改变 this 指向。
优点:
- 可以在子类构造函数中向父类构造函数传参。
- 避免了引用类型共享问题。
缺点:
- 无法继承父类原型上的属性和方法。
- 方法无法复用,每次创建实例都会重新定义一次方法。
示例代码:
javascript
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
function Child(name, age) {
Parent.call(this, name); // 核心:调用父类构造函数
this.age = age;
}
let child1 = new Child("Alice", 10);
child1.colors.push("green");
console.log(child1.colors); // ["red", "blue", "green"]
let child2 = new Child("Bob", 12);
console.log(child2.colors); // ["red", "blue"]
✅ 3. 组合继承(Combination Inheritance)
原理:
结合原型链继承和构造函数继承,使用原型链继承原型上的方法,使用构造函数继承实例属性。
优点:
- 能继承父类原型上的方法。
- 避免引用类型共享。
- 可以传参。
缺点:
- 父类构造函数被调用两次(一次在创建子类原型,一次在子类构造函数中)。
示例代码:
javascript
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.sayName = function () {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 构造函数继承
this.age = age;
}
Child.prototype = new Parent(); // 原型链继承
Child.prototype.constructor = Child; // 修正构造函数指向
let child1 = new Child("Alice", 10);
child1.colors.push("green");
console.log(child1.colors); // ["red", "blue", "green"]
let child2 = new Child("Bob", 12);
console.log(child2.colors); // ["red", "blue"]
✅ 4. 原型式继承(Prototypal Inheritance)
原理:
借助一个空对象作为中介,将某个对象作为原型创建新对象。
优点:
- 简单,适合不创建构造函数的情况下实现对象继承。
缺点:
- 引用类型会被共享。
- 不适合需要传参的场景。
示例代码(ES5 Object.create
):
javascript
let parent = {
colors: ["red", "blue"],
sayHello: function () {
console.log("Hello");
}
};
let child1 = Object.create(parent);
child1.colors.push("green");
let child2 = Object.create(parent);
console.log(child2.colors); // ["red", "blue", "green"]
✅ 5. 寄生组合式继承(Parasitic Combination Inheritance)
原理:
组合继承的优化版本,避免父类构造函数被调用两次。使用 Object.create()
创建父类原型的副本作为子类原型。
优点:
- 高效。
- 避免了构造函数重复调用。
- 完整继承了父类的实例属性和原型方法。
示例代码:
javascript
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.sayName = function () {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 构造函数继承
this.age = age;
}
// 核心:使用 Object.create 创建父类原型的副本
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
let child = new Child("Alice", 10);
child.sayName(); // Alice
✅ 6. ES6 类继承(Class Inheritance)
原理:
ES6 引入了 class
和 extends
关键字,底层仍然是原型链继承的语法糖。
优点:
- 语法更清晰,接近传统面向对象语言。
- 支持
super
、静态方法、getter/setter 等。
示例代码:
javascript
class Parent {
constructor(name) {
this.name = name;
this.colors = ["red", "blue"];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类构造函数
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
let child = new Child("Alice", 10);
child.colors.push("green");
console.log(child.colors); // ["red", "blue", "green"]
let child2 = new Child("Bob", 12);
console.log(child2.colors); // ["red", "blue"]
📌 总结对比表
继承方式 | 是否继承原型方法 | 是否共享引用 | 是否可传参 | 是否调用两次构造函数 | 是否推荐 |
---|---|---|---|---|---|
原型链继承 | ✅ | ❌ | ❌ | ❌ | ❌ |
构造函数继承 | ❌ | ✅ | ✅ | ❌ | ❌ |
组合继承 | ✅ | ✅ | ✅ | ✅ | ⚠️ |
原型式继承 | ✅ | ❌ | ❌ | ❌ | ❌ |
寄生组合式继承 | ✅ | ✅ | ✅ | ❌ | ✅ |
ES6 类继承 | ✅ | ✅ | ✅ | ❌ | ✅ |
✅ 推荐使用
- 现代开发(ES6+) :使用 ES6 类继承 (
class + extends
),语法清晰,功能强大。 - ES5 环境 :推荐使用 寄生组合式继承,性能最好,继承最完整。
如果你正在学习 JavaScript 面向对象编程,理解这些继承方式是非常有帮助的,它能帮助你写出更高效、可维护的代码。需要我为你出一个继承方式的对比图或练习题,也可以继续问我!