继承
6.3.1 原型链
原型链作为继承的主要方法:基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
以上代码定义了两个构造函数:SuperType
和Subtype
,每个类型分别有一个属性和方法,它们的主要区别是SubType
继承了SuperType
。
而继承是通过创建SuperType
的实例,并将该实例赋值给SubType.prototype
实现的。
实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于SuperType
中的属性和方法现在也存在于SubType.prototype
中了。
在确立了继承关系后,我们给SubType.prototype
添加了一个方法,这样就在继承了SuperType
的属性和方法的基础上又添加了一个新方法。
最终结果是这样的:
instance
指向SubType
的原型
SubType
的原型又指向SuperType
的原型
getSuperValue()
仍然还在SuperType.prototype
中,但property
则位于SubType.prototype
中。这是因为property
是一个实例属性,而getSuperValue()
则是一个原型方法
-
注意:
instance.constructor
现在指向的是SuperType
,这是因为原来SubType.prototype
中的constructor被重写了的原因。 -
确定原型和实例的关系:
instanceof
操作符isPrototypeOf()
方法
-
原型链继承的缺点:
-
子构造函数的所有实例都会共享父构造函数定义的
引用类型值属性
。function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ } //inherit from SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green,black"
我们对
instance1.colors
的修改能够通过instance2.colors
反映出来,就已经充分证明这这一点。 -
在创建子类型的实例的时候不能向超类型的构造函数中传递参数。
-
6.3.2 借用构造函数
为了解决原型链中包含引用类型值所带来的问题,开发人员开始使用一种叫做借用构造函数的技术。
-
基本思想:在子类型构造函数的内部调用超类型构造函数(函数只是在特定环境中执行代码的对象,因此通过使用apply()和call()方法也可以在新创建的对象上执行构造函数。
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ // inherit from SuperType // here, this will point to instance of SubType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
代码中加背景的那一行代码“借调”了超类型的构造函数。
通过使用
call()``和apply()
方法,我们实际上是在新创建的SubType
实例的环境中调用了SuperType
构造函数。这样一来,就会在新
SubType
对象上执行SuperType()
函数中定义的所有对象初始化代码,结果SubType
的所有实例都会具有自己的colors
属性的副本了。 -
传递参数
相比于原型链继承,借用构造函数有一个很大的优势,可以在子类型的构造函数中向超类型构造函数传递参数。
function SuperType(name){ this.name = name; } function SubType(){ //inherit from SuperType passing in an argument SuperType.call(this, "Nicholas"); //instance property this.age = 29; } var instance = new SubType(); alert(instance.name); //"Nicholas"; alert(instance.age); //29
-
借用构造函数的问题
如果仅仅是借用构造函数,那么就无法避免构造函数模式存在问题,方法都在构造函数中定义,因此函数复用就无从谈起了。
在超类型的原型中定义的方法,对于子类型而言是不可见的。
6.3.3 组合继承
-
组合继承指的是将原型链和借用构造函数的技术组合在一起
-
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。
-
既通过在原型上定义方法实现了函数的复用,又能够保证内个实例都有自己的属性。
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
在这个例子中,
SuperType
定义了两个属性:name
和colors
。SuperType
的原型定义了一个方法sayName()
。SubType
构造函数在调用SuperType
构造函数时传入了name
参数,紧接着又定义了它自己的属性age
。然后,将
SuperType
的实例赋值给SubType
的原型,然后又在该原型上定义了方法sayAge()
这样一来,就可以让两个不同的
SubType
实例既分别拥有了自己属性——包括colors
属性,又可以使用相同的方法。 -
组合继承避免了原型链和借用构造函数继承的缺陷,融合了他们的优点,成为了JavaScript中最常用的继承模式。
-
instanceof
和isPrototypeOf
能够识别基于组合继承创建的对象。
6.3.4 原型式继承
想法:借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
function object(o) {
function F() {
};
F.prototype = o;
return new F();
}
在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"
6.3.5 寄生式继承
-
寄生式继承是与原型式继承紧密相关的一种思路
-
思路:创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。
function createAnother(original) { var clone = object(original) // 通过调用函数创建一个新对象 clone.sayHi = function() { // 以某种方式来增强这个对象 alert("hi") } return clone // 返回这个对象 }
6.3.6 寄生组合式继承
组合继承式JavaScript最常用的继承模式,不过,它也存在自己的不足。组合继承最大的问题是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类原型的时候,另一次是在子类构造函数内部。
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);
};
第一次调用SuperType()
函数时,SuperType.prototype
会得到两个属性:name
,colors
;它们都是SuperType
的实例属性,只不过现在位于SubType
的原型中。当调用SubType
构造函数时,又会调用一次SuperType
构造函数,这一次又在新对象上创建了实例属性name
和colors
。于是,这个属性就屏蔽了原型中的两个同名属性。
也就是说,有两组name和colors属性:一组在示例上,一组在SubType
原型中。 这就是调用两次SubType
构造函数的结果。
好在我们已经找到了解决这个问题的方法——寄生组合式继承。
-
寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法
-
基本思路:不必为了指定子类型的原型而调用超类型的构造函数,我们想要的无非就是超类型原型的一个副本而已。
-
本质:使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。寄生组合式继承的基本模式如下所示:
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype) // 创建对象(超类型原型的一个副本)
prototype.constructor = subType // 增强对象(为创建的副本添加constructor属性)
subType.prototype = prototype // 指定对象
}
function object(o){
function F(){
}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //create object
prototype.constructor = subType; //augment object
subType.prototype = prototype; //assign object
}
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);
};
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
这个例子的高效性体现在它只调用了一次SuperType
构造函数,并且因此避免了在SubType.prototype
上创建不必要的、多余的属性。与此同时,原型链还能保持不变;还能够正常使用instanceof
和isPrototypeOf()