- 原型链继承
- 借用构造继承
- 组合继承
- 原型式继承
- 寄生式继承
- 寄生组合式继承
一 、原型链继承
本质:通过将一个构造函数的实例赋值给另一个构造函数的原型,实现是重写原型对象,代之以一个新类型的实例。
function SuperType(){
this.property = true; // 实例属性
}
SuperType.prototype.getSuperValue = function(){ //原型方法
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType ,替换原型语句
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
这个例子中SubType的原型被新原型SuperType实例替代了,原来存在于SuperType实例中所有属性和方法现在也存在于SubType.prototype中,在确立继承后,又给SubType添加一个新的方法,这样就在继承了SuperType实例中所有属性和方法之上又添加了一个新的方法。
最终结果instance指向SubType的原型,SubType原型又指向SuperType原型。getSuperValue方法任然还在SuperType.prototype中,但是property则位于SubType.prototype中。因为property属性是一个实例属性,而getSuperValue则是一个原型方法,既然SubType.prototype现在是SuperType的实例,那么property当然就位于该实例中。
注意:
- 所有引用类型都继承于Object,所有函数的默认原型都是Object实例,默认的原型链中包含一个内部指针,指向Object.prototype。
- 给原型SubType添加方法一定要放在替换原型语句之后。
- 通过原型链实现继承时,不能通过对象字面量创建原型方法,这样会重写原型链。例如
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,会导致上一行代码无效
SubType.prototype = {
getSubValue : function (){
return this.subproperty;
},
someOtherMethod : function (){
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!
存在的问题:
- 所有实例共享同一个包含引用类型值的原型属性,一个实例修改了属性,另一个实例中获得的属性也会被修改;
- 创建子类型实例时,不能向超类型中传递参数;
确定实例和原型的关系:
1)instanceof操作符
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
2)isPrototypeOf()方法
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
二、借用构造继承
本质:在子类型构造函数内部调用超类型构造函数——call()和apply()
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了SuperType,同时还传递了参数
SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
通过使用call()和apply()实际在每次新创建的SubType实例的环境中都重新调用了SuperType构造函数,这样就会在新SubType对象上执行SuperType函数中定义的所有对象初始化代码。结果SubType的每个实例就会具有自己的属性的副本了。
存在问题:方法都在构造函数中定义,因此函数复用就无从谈起了。
三、组合继承
本质:使用原型链实现对原型方法的继承,而通过借用构造函数来实现对原型属性的继承。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
第一步:Subtype构造函数在调用SuperType构造函数时传入name参数,又定义了自己的属性age。
第二步:将SuperType实例赋值给Subtype原型,又定义了自己的方法sayAge
这样使得Subtype不同的实例拥有原型属性的新副本,又共享同一个原型方法。
缺点:调用两次超类型的构造函数
四、原型式继承
本质:要求必须有一个对象作为另一个对象的基础,然后将这个基础对象传入object()函数做了一个浅复制,然后再根据具体需求对得到的对象加以修改(对象的继承)。
使用场景:在没有必要创建构造函数,只想让一个对象与另一个对象保持类似的情况下可以使用。
object()函数如下,在函数内部,先创建一个临时性的构造函数,然后传入的对象作为这个构造函数的原型,最后返回这个构造函数的新实例。
function object(o){
function F(){}
F.prototype = o;
return new 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");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
ES5中Object.create()方法规范化原型式继承,接受两个参数:一个是基础对象,一个是为新对象定义额外属性的对象(可选)。当只有一个参数时,和上面定义的Object函数作用相同;如果有第二个参数,这个对象是通过自己的描述符定义的,以这种方式指定的任何属性都会覆盖原型对象上的属性。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
name: {
value: "Greg"
}
})
;
alert(anotherPerson.name); //"Greg"
缺点:内存占用率高,因为拷贝了父类型的属性
五、寄生式继承
本质:创建一个仅用于封装继承过程的函数,该函数内部已某种方式来增强对象,最后再返回对象。(在原生式继承基础上做了封装)
function createAnother(original){
var clone = object(original); //通过调用函数创建一个新对象
clone.sayHi = function(){ //以某种方式来增强这个对象
alert("hi");
};
return clone; //返回这个对象
}
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
createAnother函数接受一个参数,也就是作为新对象基础的对象,然后将这个对象传递给object函数,将返回的结果赋值给clone,再给clone对象添加新的方法,最后返回对象。
适用场景:在主要考虑对象而不是自定义类型和构造函数情况下适用。任何能够返回新对象的函数都适用此模式。
缺点:实例是父类的实例,不是子类的实例
六、寄生组合式继承
由于组合继承无论在什么情况下,都会调用两次超类型构造函数,如下。为了解决这个问题提出来寄生组合继承
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name); //第二次调用
SuperType()
this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用
SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
本质:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。即使用寄生式继承来继承超类型的原型,然后将结果指定给子类型的原型。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //创建对象
prototype.constructor = subType; //增强对象
subType.prototype = prototype; //指定对象
}
这个函数接受两个参数:一个是子类型构造函数,一个是超类型构造函数。在函数内部,第一步创建超类型原型的一个副本,第二步为创建的副本添加constructor属性,从而弥补因重写原型而丢失的默认的constructor属性,最后一步将创建的对象复制给子类型的原型。实例:
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};