实现类的继承
理解 JavaScript 中实现类继承的多种方式,包括原型链继承、构造函数继承、组合继承等经典模式
问题
在 JavaScript 中实现类的继承机制,使子类能够继承父类的属性和方法。需要掌握多种继承方式的实现原理、优缺点,以及 ES6 class 语法糖背后的实现机制。
解答
方式一:原型链继承
// 父类
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
// 子类
function Child(age) {
this.age = age;
}
// 实现继承
Child.prototype = new Parent('parent');
Child.prototype.constructor = Child;
方式二:构造函数继承(借用构造函数)
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;
}
方式三:组合继承(推荐)
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;
Child.prototype.sayAge = function() {
console.log(this.age);
};
方式四:寄生组合式继承(最优解)
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;
Child.prototype.sayAge = function() {
console.log(this.age);
};
方式五:ES6 Class 继承
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);
}
}
通用继承函数封装
/**
* 实现寄生组合式继承
* @param {Function} Child - 子类构造函数
* @param {Function} Parent - 父类构造函数
*/
function inherit(Child, Parent) {
// 创建父类原型的副本
Child.prototype = Object.create(Parent.prototype);
// 修正构造函数指向
Child.prototype.constructor = Child;
// 保存父类引用(可选)
Child.super = Parent;
}
// 使用示例
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inherit(Child, Parent);
使用示例
// 寄生组合式继承示例
function Animal(name) {
this.name = name;
this.energy = 100;
}
Animal.prototype.eat = function() {
this.energy += 10;
console.log(`${this.name} is eating, energy: ${this.energy}`);
};
Animal.prototype.sleep = function() {
console.log(`${this.name} is sleeping`);
};
function Dog(name, breed) {
// 继承属性
Animal.call(this, name);
this.breed = breed;
}
// 继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// 添加子类特有方法
Dog.prototype.bark = function() {
this.energy -= 5;
console.log(`${this.name} is barking! Woof! Energy: ${this.energy}`);
};
// 测试
const dog = new Dog('旺财', '哈士奇');
console.log(dog.name); // 旺财
console.log(dog.breed); // 哈士奇
dog.eat(); // 旺财 is eating, energy: 110
dog.bark(); // 旺财 is barking! Woof! Energy: 105
dog.sleep(); // 旺财 is sleeping
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
// ES6 Class 继承示例
class Shape {
constructor(color) {
this.color = color;
}
getColor() {
return this.color;
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color); // 必须先调用 super
this.radius = radius;
}
getArea() {
return Math.PI * this.radius ** 2;
}
}
const circle = new Circle('red', 5);
console.log(circle.getColor()); // red
console.log(circle.getArea()); // 78.53981633974483
关键点
-
原型链继承:子类原型指向父类实例,缺点是所有实例共享引用类型属性,且无法向父类构造函数传参
-
构造函数继承:通过
call/apply调用父类构造函数,解决了属性共享问题,但无法继承父类原型上的方法 -
组合继承:结合原型链和构造函数继承,是最常用的方式,缺点是会调用两次父类构造函数
-
寄生组合式继承:使用
Object.create()创建父类原型副本,避免调用两次构造函数,是最优的继承方案 -
关键步骤:
- 使用
Parent.call(this)继承实例属性 - 使用
Object.create(Parent.prototype)继承原型方法 - 修正
constructor指向子类
- 使用
-
ES6 Class:本质是语法糖,底层仍是原型继承,但语法更清晰,必须在子类构造函数中先调用
super() -
instanceof 检测:正确实现继承后,子类实例既是子类的实例,也是父类的实例
-
注意事项:修正
constructor指向很重要,否则会影响实例的构造函数判断
目录