目录
一、使用原型链继承
1.1 原型链的基本概念
将父类型的实例作为子类型的原型对象,以此构成的链式关系叫做原型链。
原型链式继承的本质是重写子类型的原型对象,代之以一个父类型的实例。
原理图如下:
注意:此时的instance.constructor指向的是SuperType
1.2 默认的原型 —— Object实例
所有函数的默认原型都是Object的实例,所以上面例子完整的原型链是这样的:
1.3 确定原型与实例的关系
只要是原型链中出现过的原型,都可以说是该原型链派生的实例的原型。
因此,可以通过两种方法来确定原型和实例之间的关系:
第一种:instanceof操作符
第二种:isPrototypeOf()方法
1.4 原型链的缺点
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){}
//继承了SuperType
SubType.prototype = new SubType();
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors); //"red,blue,green,black"
如上面代码所示,所有子类型实例(instance1、instance2)共享父类型的引用类型的属性(colors)。
所以原型链的缺点是:
- 所有子类型实例共享父类型的引用类型的属性。
- 创建子类型的实例时,不能向父类型的构造函数中传递参数。
二、经典继承(借用构造函数)
经典继承也叫借用构造函数或伪造对象。
由于函数只是在特定环境中执行代码的对象,所以可以通过使用apply()和call()方法改变父类型的构造函数的执行环境,从而达到继承的目的。如下所示:
function SuperType(name){
this.colors = ["red", "blue", "green"];
this.name = name;
}
function SubType(){
//借用父类型的构造函数
//此时父类型的构造函数的this的指向与子类型的构造函数的this的指向相同
SuperType.call(this, "Nicholas");
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors); //"red,blue,green"
console.log(instance2.name); //"Nicholas"
经典继承实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数。
相对于原型链式继承,经典继承可以在子类型构造函数中向超类型构造函数传递参数。
2.1 经典继承的缺点
父类型方法都在构造函数中定义,因此子类型无法实现函数复用。
三、组合继承
组合继承指的是将原型链和经典继承的技术组合到一起,从而发挥二者之长的一种继承模式。
即使用原型链实现对原型属性和方法的继承,而通过经典继承来实现对实例属性的继承。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){ //原型方法
console.log(this.name);
};
function SubType(name, age){
//继承实例属性
SuperType.call(this, name);
this.age = age;
}
//继承原型方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; //令constructor指向子类型
SubType.prototype.sayAge = function(){
console.log(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas"
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg"
instance2.sayAge(); //27
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。
四、原型式继承
原型式继承的核心思想是:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
为了达到上面的目的,需要先声明如下函数:
function object(o){
function F(){}
F.prototype = o;
return new F();
}
在object()函数内部,先创建了一个临时性的构造函数F,然后将传入的对象o作为这个构造函数F的原型,最后返回了这个临时类型的一个新实例。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); //"Shelby,Court,Van,Rob,Barbie"
ECMAScript5通过新增Object.create()方法规范化了原型式继承。
这个方法接受两个参数:
- 用作新对象原型的对象
- 为新对象定义额外属性的对象,这个参数与Object.defineProperties()方法的第二个参数格式相同
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
});
console.log(anotherPerson.name); //"Greg"
五、寄生式继承
寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象。
function createAnother(original){
var clone = Object.create(original); //通过调用Object.create()创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
console.log("hi");
};
return clone; //返回这个对象
}
可以像下面这样来使用createAnother()函数:
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
5.1、寄生式继承的缺点
使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率,这一点与构造函数模式类似。
六、寄生组合式继承
6.1、组合继承的缺点
组合继承最大的问题就是无论什么情况下,都会调用两次超类型构造函数:
一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
因此,子类型的原型对象会包含父类型对象的全部实例属性,但第二次调用子类型构造函数时重写了这些属性。重写属性降低了效率。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function(){
console.log(this.name);
};
function SubType(name, age){
SuperType.call(this, name); //第二次调用SuperType(),将会重写"name"和"colors"属性
this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
console.log(this.age);
}
6.2、寄生组合式继承的原理
寄生组合式继承的基本思路是:不必为了指定子类型的原型而调用父类型的构造函数,我们所需要的无非就是父类型原型的一个副本而已。
寄生组合式继承的基本模式如下:
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function(){
console.log(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var instance = new SubType("Nicholas", 29);
console.log(instance.name); //"Nicholas"
instance.sayName(); //"Nicholas"
这个例子的高效率体现在:
- 只调用了一次SuperType构造函数
- 避免了在SubType.prototype上面创建不必要的、多余的属性
- 原型链仍保持不变,因此,还能够正常使用instanceof和isPrototypeOf()