JavaScript 中的原型继承有以下几种方式:
原型链继承:通过将子类的原型设置为父类的实例对象来实现继承。缺点是父类的引用类型属性会被所有子类实例共享。构造函数继承(借助构造函数):利用父类的构造函数来增强子类实例,即在子类构造函数中调用父类构造函数。缺点是无法继承父类的原型上的属性和方法。组合继承(融合原型链继承和借助构造函数继承):使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。缺点是会调用两次父类构造函数,导致效率低下。原型式继承:用一个临时构造函数作为中介,将某个对象直接赋值给临时构造函数的原型,然后返回这个临时构造函数的一个实例对象。缺点是如果没有正确处理好引用类型的属性,可能会导致它们之间相互影响。寄生式继承:创建一个实现继承的函数,以某个对象为基础,并进行增强,最后返回这个对象。可用于给对象添加一些额外的属性或方法,但也可能会像原型式继承一样导致引用类型属性的共享。寄生组合式继承(最优解):通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。既不会调用两次父类构造函数,也不会让实例与原型之间多余的属性和方法。
举一个简单的例子来说明这几种继承方式。
function Animal(name) {
this.name = name;
}
Animal.prototype.say = function() {
console.log(`I am ${this.name}.`);
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
}
现在我们想创建一个 Cat 类来继承 Animal,以下是各种继承方式的实现:
- 原型链继承:
function Cat(name, color) {
Animal.call(this, name);
this.color = color;
}
Cat.prototype = new Animal();
const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."
- 构造函数继承(借助构造函数):
function Cat(name, color) {
Animal.call(this, name);
this.color = color;
}
// 将子类的原型指向父类的原型实现对父类原型属性和方法的继承
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
// undefined
- 组合继承(融合原型链继承和借助构造函数继承):
function Cat(name, color) {
Animal.call(this, name);
this.color = color;
}
Cat.prototype = new Animal();
const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."
- 原型式继承:
function createObj(o) {
function F() {}
F.prototype = o;
return new F();
}
const cat = createObj(Animal.prototype);
cat.say(); // output: undefined
- 寄生式继承:
function createCat(obj) {
const cat = Object.create(obj);
cat.color = 'white';
cat.meow = function() {
console.log('Meow~');
}
return cat;
}
const cat = createCat(new Animal('Kitty'));
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."
cat.meow(); // output: "Meow~"
- 寄生组合式继承:
function Cat(name, color) {
Animal.call(this, name);
this.color = color;
}
function inheritPrototype(subType, superType) {
const prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
inheritPrototype(Cat, Animal);
const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."
ES6 的 Class 继承其实使用的是组合继承,即通过 extends 关键字实现原型链继承,通过 super() 方法调用父类的构造函数实现借助构造函数继承。这种方式与传统的组合继承相似,但比传统的组合继承更简单和直观。
class Animal {
constructor(name) {
this.name = name;
}
say() {
console.log(`I am ${this.name}.`);
}
eat() {
console.log(`${this.name} is eating.`);
}
}
class Cat extends Animal {
constructor(name, color) {
super(name);
this.color = color;
}
}
const cat = new Cat('Kitty', 'white');
cat.say(); // output: "I am Kitty."
cat.eat(); // output: "Kitty is eating."