1. 原型链继承
原理:通过修改子类的原型(prototype
)为父类的实例,实现属性和方法的继承。
示例:
javascript
function Parent() {
this.name = 'parent';
this.hobbies = ['reading'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child() {
this.age = 18;
}
// 核心:将Child的原型指向Parent的实例
Child.prototype = new Parent();
Child.prototype.constructor = Child; // 修复构造函数指向
const child1 = new Child();
const child2 = new Child();
child1.hobbies.push('swimming');
console.log(child2.hobbies); // ['reading', 'swimming'](被修改)
优点:
- 实现简单,符合原型链的自然继承逻辑。
- 父类原型上的方法可被所有子类实例共享,节省内存。
缺点:
- 引用类型属性共享问题:父类实例的引用类型属性(如数组、对象)会被所有子类实例共享,修改一个实例会影响其他实例。
- 无法向父类传参:创建子类实例时,无法为父类构造函数传递参数。
- 原型污染风险:直接修改原型可能影响所有实例。
2. 构造函数继承(借用构造函数)
原理:在子类构造函数中通过 call()
或 apply()
调用父类构造函数,将父类的属性 "复制" 到子类实例中。
示例:
javascript
function Parent(name) {
this.name = name;
this.hobbies = ['reading'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 借用父类构造函数
this.age = age;
}
const child = new Child('child', 18);
console.log(child.name); // 'child'
console.log(child.hobbies); // ['reading']
console.log(child.sayName); // undefined(无法继承原型方法)
优点:
- 解决引用类型共享问题:每个子类实例都有独立的属性副本。
- 可向父类传参:创建子类时可灵活初始化父类属性。
缺点:
- 无法继承父类原型方法:子类只能继承父类构造函数中的属性,无法访问父类原型上的方法,导致方法无法复用。
- 内存浪费:每个子类实例都有独立的方法副本,造成冗余。
3. 组合继承(原型链 + 构造函数)
原理:结合原型链和构造函数继承的优点:用原型链继承父类原型方法,用构造函数继承父类实例属性。
示例:
javascript
function Parent(name) {
this.name = name;
this.hobbies = ['reading'];
}
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;
const child1 = new Child('child1', 18);
const child2 = new Child('child2', 20);
child1.hobbies.push('swimming');
console.log(child2.hobbies); // ['reading'](未被修改)
child1.sayName(); // 'child1'
优点:
- 既继承了父类原型方法(共享复用),又避免了引用类型属性共享问题。
- 可向父类构造函数传参,灵活性高。
缺点:
- 父类构造函数被调用两次:第一次是
new Parent()
创建原型对象时,第二次是子类构造函数中Parent.call(this)
。这会导致原型对象和子类实例中都存在父类的属性副本,造成冗余。
4. 寄生组合继承
原理:通过创建一个空构造函数作为中间层,避免组合继承中父类构造函数的重复调用。
示例:
javascript
function Parent(name) {
this.name = name;
this.hobbies = ['reading'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 继承实例属性
this.age = age;
}
// 核心:创建空构造函数作为中间层,避免重复调用Parent
function createPrototype(SuperType) {
function F() {}
F.prototype = SuperType.prototype;
return new F();
}
Child.prototype = createPrototype(Parent);
Child.prototype.constructor = Child;
const child = new Child('child', 18);
console.log(child instanceof Parent); // true
优点:
- 避免重复调用父类构造函数:仅继承父类原型方法,不创建父类实例属性。
- 性能最优:是现代 JavaScript 中常用的继承方式。
缺点:
- 代码复杂度较高,需要手动修复
constructor
指向。
5. ES6 Class 继承
原理:使用 class
和 extends
关键字实现继承,本质上是寄生组合继承的语法糖。
示例:
javascript
class Parent {
constructor(name) {
this.name = name;
this.hobbies = ['reading'];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类构造函数
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
const child = new Child('child', 18);
console.log(child instanceof Parent); // true
child.sayName(); // 'child'
优点:
- 语法简洁:符合面向对象编程范式,代码可读性高。
- 支持
super
关键字:方便访问父类方法和属性。 - 内置类型继承:可直接继承
Array
、Map
等内置类。
缺点:
- 依赖 ES6 环境:需兼容旧版浏览器时需使用 Babel 转译。
- 原理复杂:内部仍依赖原型链和构造函数机制。
6. 混入(Mixin)继承
原理:通过合并多个对象的属性和方法实现 "多继承"。
示例:
javascript
const Flyable = {
fly() {
console.log('I can fly');
}
};
const Swimmable = {
swim() {
console.log('I can swim');
}
};
class Bird {
constructor() {
this.name = 'bird';
}
}
// 将Flyable和Swimmable的方法混入Bird
Object.assign(Bird.prototype, Flyable, Swimmable);
const bird = new Bird();
bird.fly(); // 'I can fly'
bird.swim(); // 'I can swim'
优点:
- 灵活组合功能:可按需合并多个对象的属性和方法。
- 实现多继承:JavaScript 原生不支持多继承,混入是一种替代方案。
缺点:
- 命名冲突风险:多个混入对象可能包含同名方法,导致覆盖。
- 破坏封装性:混入对象的内部状态可能被外部访问和修改。
总结对比
继承方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
原型链继承 | 实现简单,共享原型方法 | 引用类型共享,无法传参 | 简单继承,无引用类型属性 |
构造函数继承 | 避免引用类型共享,可传参 | 无法继承原型方法 | 仅需继承实例属性 |
组合继承 | 继承完整,无共享问题 | 父类构造函数调用两次 | 需继承完整功能 |
寄生组合继承 | 性能最优,避免重复调用 | 代码复杂 | 高性能场景 |
ES6 Class 继承 | 语法简洁,支持 super | 依赖 ES6 环境 | 现代 JavaScript 开发 |
混入继承 | 灵活组合多对象功能,实现多继承 | 命名冲突,破坏封装性 | 需要多继承或功能组合的场景 |
推荐:在现代 JavaScript 中,优先使用 ES6 Class 继承 或 寄生组合继承,它们在性能和可维护性上表现最佳。