1. 原型链继承:
- 特点: 使用原型链实现对象之间的继承关系。
- 缺点: 引用类型的属性会被所有实例共享,容易造成修改的混乱,且在创建子类型时不能向超类型传递参数。
类比: 想象成一个大家族,所有家庭成员共享同一份家谱,但无法为每个家庭成员定制个性化的信息。
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating.');
};
function Dog(breed) {
this.breed = breed;
}
Dog.prototype = new Animal('GenericAnimal');
var myDog = new Dog('Golden Retriever');
myDog.eat(); // 输出: GenericAnimal is eating.
向超类型传递参数
"向超类型传递参数" 意味着在使用构造函数模式实现继承时,能够将子类型的构造函数中接收的参数传递给超类型的构造函数,以便在创建子类型实例时能够对超类型进行初始化。
在JavaScript中,构造函数可以接收参数,并且通过使用
call、apply或者bind等方法,可以在一个构造函数中调用另一个构造函数,并传递参数。这样就实现了在子类型中调用超类型构造函数,并将参数传递给超类型。以下是一个简单的示例,演示了如何向超类型传递参数:
// 超类型(父类) function Animal(name) { this.name = name; this.energy = 100; // 默认能量值 } // 超类型的方法 Animal.prototype.eat = function() { console.log(this.name + ' is eating.'); this.energy += 10; }; // 子类型(子类) function Dog(name, breed) { // 在子类型构造函数中调用超类型构造函数,并传递参数 Animal.call(this, name); // 在这里,this指向当前创建的Dog实例 this.breed = breed; } // 创建子类型的实例 var myDog = new Dog('Buddy', 'Golden Retriever'); // myDog继承了父类的属性和方法 console.log(myDog.name); // 输出: Buddy console.log(myDog.energy); // 输出: 100 // 调用继承的方法 myDog.eat(); // 输出: Buddy is eating. console.log(myDog.energy); // 输出: 110在上面的例子中,
Dog构造函数调用了Animal构造函数,并通过call(this, name)将name参数传递给了Animal构造函数。这样,myDog实例既拥有了Dog构造函数中定义的属性breed,又继承了Animal构造函数中的属性和方法。在上面的代码中,
Animal.call(this, name);的目的是将Animal构造函数的上下文(this的上下文)设置为当前正在创建的Dog实例。这样,当Animal构造函数内部使用this时,它指向的是新创建的Dog实例,而不是Animal构造函数内部的默认上下文。
2. 构造函数继承:
- 特点: 通过在子类型构造函数中调用超类型构造函数实现继承,解决了无法向超类型传递参数的问题。
- 缺点: 不能继承超类型原型上定义的方法,无法实现方法的复用。
类比: 子类型好像搬到了超类型的房子里,可以带些自己的家具,但不能使用超类型房子原本的设施。
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating.');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
var myDog = new Dog('Buddy', 'Golden Retriever');
myDog.eat(); // 输出: Buddy is eating.
3. 组合继承:
将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。
- 特点: 结合了原型链继承和构造函数继承,解决了各自单独使用时的问题。
- 缺点: 调用了两次超类的构造函数,导致子类型的原型中包含不必要的属性。
类比: 子类型既有自己的房子,也可以使用超类型的设施,但房子的结构可能有些多余。
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating.');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = new Animal();
var myDog = new Dog('Buddy', 'Golden Retriever');
myDog.eat(); // 输出: Buddy is eating.
4. 原型式继承:
原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象, 然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。
- 特点: 基于已有对象创建新对象,通过Object.create()实现,适用于简单继承。
- 缺点: 与原型链方式相同,引用类型属性会被所有实例共享。
类比: 像是通过复制一张已有图纸来创建新的图纸,但所有图纸共享相同的设计。
var animal = {
name: 'GenericAnimal',
eat: function() {
console.log(this.name + ' is eating.');
}
};
var dog = Object.create(animal, { breed: { value: 'Golden Retriever' } });
dog.eat(); // 输出: GenericAnimal is eating.
5. 寄生式继承:
寄生式继承的思路是创建一个用于 封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本, 然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解 是一种继承
- 特点: 创建一个用于封装继承过程的函数,通过复制对象并进行扩展实现继承。
- 缺点: 不能实现函数的复用。
类比: 好比购买了一个现成的家具,然后为了增加一些功能,加了一些自定义的部分。
function createDog(breed) {
var dog = Object.create({
eat: function() {
console.log(this.name + ' is eating.');
}
});
dog.breed = breed;
return dog;
}
var myDog = createDog('Golden Retriever');
myDog.eat(); // 输出: undefined is eating.
在这个例子中,createDog 函数返回一个新的对象,但这个对象的 bark 方法是在函数内部定义的,而且它没有被抽象成一个可以在其他地方复用的函数。如果我们再次调用 createDog 创建另一个对象,每次都会创建一个新的 bark 方法,而无法实现真正的函数复用。
6. 寄生式组合继承:
- 特点: 使用超类型原型的副本作为子类型的原型,解决了组合继承中不必要的属性问题。组合继承的缺点就是使用超类 型的实例做为子类型的原型,导致添加了不必要的原型属性。
- 优点: 保留了原型链继承和构造函数继承的优点,避免了它们各自的缺点。
类比: 子类型既有自己的房子,也可以使用超类型的设施,但是通过复制超类型的设计来避免了多余的结构。
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating.');
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
// 解决了组合继承中调用两次超类构造函数的问题
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
var myDog = new Dog('Buddy', 'Golden Retriever');
myDog.eat(); // 输出: Buddy is eating.