JavaScript基础:继承的方法及优缺点
1. 原型链继承 (Prototype Chain Inheritance)
原型链继承是 JavaScript 中最早的继承方法之一,它通过将子类的原型指向父类的实例,来实现子类对父类属性和方法的继承。
示例代码:
function Parent() {
this.name = 'Parent';
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child() {
this.age = 18;
}
Child.prototype = new Parent(); // 子类原型指向父类实例
const child1 = new Child();
child1.colors.push('yellow');
console.log(child1.colors); // ['red', 'blue', 'green', 'yellow']
const child2 = new Child();
console.log(child2.colors); // ['red', 'blue', 'green', 'yellow']
优点:
- 子类可以访问父类的属性和方法。
- 父类的实例方法可以通过子类继承,避免重复定义。
缺点:
- 引用类型的问题:父类中引用类型(如数组、对象)会被所有子类实例共享,导致一个子类实例的修改会影响其他子类实例。
- 无法向父类构造函数传参:在创建子类实例时,无法向父类构造函数传递参数,这限制了灵活性。
2. 借用构造函数继承 (Constructor Stealing / Classical Inheritance)
借用构造函数继承,也称为“经典继承”,通过在子类的构造函数中调用父类的构造函数来实现属性的继承。
示例代码:
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
function Child(name, age) {
Parent.call(this, name); // 调用父类构造函数
this.age = age;
}
const child1 = new Child('Child1', 18);
child1.colors.push('yellow');
console.log(child1.colors); // ['red', 'blue', 'green', 'yellow']
console.log(child1.name); // 'Child1'
const child2 = new Child('Child2', 20);
console.log(child2.colors); // ['red', 'blue', 'green']
console.log(child2.name); // 'Child2'
优点:
- 解决了原型链继承中引用类型共享的问题,因为每次调用父类构造函数时,都会重新创建属性。
- 可以向父类构造函数传递参数,使继承更加灵活。
缺点:
- 无法继承父类原型上的方法:父类的实例方法无法被子类继承,所有方法只能写在构造函数内部,这会导致每个子类实例都生成相同的方法,浪费内存。
3. 组合继承 (Combination Inheritance)
组合继承结合了原型链继承和借用构造函数继承的优点:通过原型链继承父类的方法,通过借用构造函数继承父类的属性。
示例代码:
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
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);
child1.colors.push('yellow');
console.log(child1.colors); // ['red', 'blue', 'green', 'yellow']
child1.sayName(); // 'Child1'
const child2 = new Child('Child2', 20);
console.log(child2.colors); // ['red', 'blue', 'green']
child2.sayName(); // 'Child2'
优点:
- 既可以继承父类的实例属性,也可以继承父类的原型方法。
- 每个子类实例都有自己的属性,引用类型不会共享,灵活性更高。
缺点:
- 父类构造函数会被调用两次,一次是在借用构造函数时,另一次是在设置子类原型时,这导致了不必要的性能消耗。
4. 寄生组合继承 (Parasitic Combination Inheritance)
寄生组合继承是对组合继承的优化,它避免了父类构造函数被调用两次的情况。通过这种方式,子类只继承父类原型中的方法,而不调用父类构造函数来设置子类原型。
示例代码:
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 借用构造函数继承属性
this.age = age;
}
Child.prototype = Object.create(Parent.prototype); // 创建父类原型的副本
Child.prototype.constructor = Child;
const child1 = new Child('Child1', 18);
child1.colors.push('yellow');
console.log(child1.colors); // ['red', 'blue', 'green', 'yellow']
child1.sayName(); // 'Child1'
const child2 = new Child('Child2', 20);
console.log(child2.colors); // ['red', 'blue', 'green']
child2.sayName(); // 'Child2'
优点:
- 避免了组合继承中的重复调用父类构造函数的问题,提升了性能。
- 保留了组合继承的优点,能够继承父类的实例属性和原型方法。
缺点:
- 实现相对复杂,需要手动创建和重设子类的原型。
5. ES6 class 继承 (Class Inheritance)
ES6 引入了 class
语法,使继承变得更加直观和简洁。通过 extends
关键字,子类可以直接继承父类的属性和方法。
示例代码:
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类构造函数
this.age = age;
}
}
const child1 = new Child('Child1', 18);
child1.colors.push('yellow');
console.log(child1.colors); // ['red', 'blue', 'green', 'yellow']
child1.sayName(); // 'Child1'
const child2 = new Child('Child2', 20);
console.log(child2.colors); // ['red', 'blue', 'green']
child2.sayName(); // 'Child2'
优点:
- 语法简洁、直观,类结构清晰。
- 可以通过
super()
调用父类的构造函数,并继承父类的原型方法。 - 易于理解和使用,尤其对面向对象编程背景的开发者来说更为友好。
缺点:
- ES6
class
只是基于原型的语法糖,底层仍然是基于原型链实现的继承。 - 不能直接用于一些老旧的浏览器,可能需要通过 Babel 等工具进行转译。