1. 原型链继承
ECMAScript 把原型链定义为 ECMAScript 的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性(constructor)指回构造函数,而实例有一个内部指针(__proto__)指向原型。
基本思路:一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。
// 创建Animal
function Animal() {
this.name = 'animal';
}
Animal.prototype.getAnimalName = function () {
console.log(this.name);
}
// 创建Dog
function Dog() {
this.name = 'dog';
}
// Dog继承自Animal 将Animal的实例赋值给Dog的原型对象,相当于将Animal的实例中的__proto__赋值给了Dog的原型对象
Dog.prototype = new Animal();
console.log(Dog.prototype.__proto__ === Animal.prototype ); // true
// 在使用原型链继承的时候,要在继承之后再去原型对象上定义自己所需的属性和方法
Dog.prototype.getDogName = function () {
console.log(this.name);
}
var d1 = new Dog();
d1.getAnimalName(); // dog
d1.getDogName(); // dog
优点:
- 子类可以访问父类的所有属性、方法
- 实现简单方便
缺点:
- 所有子类实例共用同一个原型对象的属性
- 创建子类实例时,无法向父类构造函数传递参数
function Animal() {
this.arr = [1, 2];
}
function Dog() { }
// 原型链继承 Animal
Dog.prototype = new Animal();
var d1 = new Dog();
d1.arr.push(3);
console.log(d1.arr); // [ 1,2,3 ]
var d2 = new Dog();
console.log(d2.arr); // [ 1,2,3 ]
2. 经典继承(借用构造函数继承)
基本思路 :在子类构造函数中调用父类构造函数。
function Animal(name) {
this.name = name;
this.arr = [1, 2];
}
function Dog(name,age) {
// 经典继承 Animal
Animal.call(this,name);
this.age = age;
}
在var d1 = new Dog()时,是d1调用Dog构造函数,所以其内部this的值指向的是d1,所以Animal.call(this)就相当于Animal.call(d1),就相当于d1.Animal()。最后,d1去调用Animal方法时,Animal内部的this指向就指向了d1。那么Animal内部this上的所有属性和方法,都被拷贝到了d1上。所以,每个实例都具有自己的arr属性副本。他们互不影响。
var d1 = new Dog('d1',12);
d1.arr.push(3);
console.log(d1.arr); // [ 1,2,3 ]
var d2 = new Dog('d2',15);
console.log(d2.arr); // [ 1,2 ]
优点:
- 创建实例可以传递参数
- 解决原型链继承的共用同一个父类属性问题
- 可以实现多继承(call多个父类对象)
缺点:
- 方法只能定义在构造函数中
- 创建的实例并不是父类的实例,只是子类的实例。
- 没有拼接原型链,不能使用instanceof。因为子类的实例只继承了父类的实例属性/方法,没有继承父类的构造函数的原型对象中的属性/方法。
- 每个子类的实例都持有父类的实例方法的副本,浪费内存,影响性能,而且无法实现父类的实例方法的复用。
3. 组合继承(综合原型链继承和经典继承)
基本思路:使用原型链继承原型上的属性和方法,而通过经典继承函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
function Animal(name) {
this.name = name;
this.arr = [1,2];
}
Animal.prototype.sayName = function () {
console.log(this.name);
};
function Dog(name, age) {
// 继承属性
Animal.call(this, name); // 第二次调用Animal()
this.age = age;
}
// 继承方法
Dog.prototype = new Animal(); // 第一次调用Animal()
Dog.prototype.sayAge = function () {
console.log(this.age);
};
var d1 = new Dog("d1", 12);
d1.arr.push(3);
console.log(d1.arr); // [ 1,2,3 ]
d1.sayName(); // d1
d1.sayAge(); // 12
var d2 = new Dog("d2", 15);
console.log(d2.arr); // [ 1,2 ]
d2.sayName(); // d1
d2.sayAge(); // 15
优点:
- 弥补了原型链和经典继承函数的不足
- 保留了 instanceof 操作符和 isPrototypeOf()方法识别基于组合继承创建的对象
缺点:
- 无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。