3.繼承
3.1 原型鏈
原型鏈?zhǔn)菍?shí)現(xiàn)繼承的主要方法姑曙。其基本思想是利用原型讓一個(gè)引用類(lèi)型繼承另一個(gè)引用類(lèi)型的屬性和方法。
構(gòu)造函數(shù)黍翎、原型和實(shí)例的關(guān)系
每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象贝淤,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針郭计。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
// 使SubType繼承SuperType的實(shí)例 本質(zhì)是重寫(xiě)原型對(duì)象
SubType.prototype = new SuperType();
SubType.prototype.getValue = function(){
return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue());//true
注意 :==instance.constructor現(xiàn)在指向的是SuperType,這是因?yàn)樵瓉?lái)SubType的原型指向了另一個(gè)對(duì)象--SuperType的原型霸琴,而這個(gè)原型對(duì)象的constructor屬性指向的是SuperType==
默認(rèn)的原型 Object
確定原型與實(shí)例的關(guān)系
- instanceof:用這個(gè)操作符來(lái)測(cè)試實(shí)例與原型鏈中出現(xiàn)過(guò)的構(gòu)造函數(shù),結(jié)果就會(huì)返回true
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
- 使用isPrototypeOf()方法昭伸,只要是原型鏈中出現(xiàn)的原型沈贝,都可以說(shuō)是該原型鏈所派生的實(shí)例的原型
alert(Object.prototype isPrototypeOf(instance)); //true
alert(SuperType.prototype isPrototypeOf(instance));
alert(SubType.prototype isPrototypeOf(instance));
通過(guò)原型鏈實(shí)現(xiàn)繼承時(shí),不能使用對(duì)象字面量創(chuàng)建原型方法勋乾,因?yàn)檫@樣會(huì)重寫(xiě)原型鏈
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType();
// 使用對(duì)象字面量創(chuàng)建原型方法宋下,會(huì)重寫(xiě)原型鏈
SubType.prototype = {
getSubValue: function(){
return this.subproperty;
}
};
var instance = new SubType();
alert(instance.getSuperValue());//error!!
原型鏈的問(wèn)題
-
包含引用類(lèi)型值的原型屬性會(huì)被所有實(shí)例共享,因此在構(gòu)造函數(shù)中定義屬性而不是在原型對(duì)象中定義屬性辑莫。
在通過(guò)原型來(lái)實(shí)現(xiàn)繼承時(shí)学歧,原型實(shí)際上會(huì)變成另一個(gè)類(lèi)型的實(shí)例。于是各吨,原先的實(shí)例屬性也就變成了現(xiàn)在的原型屬性了枝笨。
function SuperType(){
this.color = ["red", "blue", "green"];
this.name = "Nicholas";
}
function SubType(){
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red","blue","green","black"
instance1.name = "Tom";
alert(instance1.name); //"Tom"
var instance2 = new SubType();
// colors是引用類(lèi)型,會(huì)被所有實(shí)例共享
alert(instance2.colors); // "red","blue","green","black"
alert(instance2.name); //"Nicholas"
- 沒(méi)有辦法在不影響所有對(duì)象實(shí)例的情況下揭蜒,給超類(lèi)型的構(gòu)造函數(shù)傳遞參數(shù)
function SuperType(name){
this.name = name;
}
function SubType(name){
}
// ??如何向超類(lèi)型的構(gòu)造函數(shù)傳遞參數(shù)横浑??
SubType.prototype = new SuperType();
var instance2 = new SubType(name);
- 綜合以上情況屉更,實(shí)踐中很少會(huì)單獨(dú)使用原型鏈
3.2 借用構(gòu)造函數(shù)
在子類(lèi)型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類(lèi)型構(gòu)造函數(shù)徙融。
function SuperType(){
this.colors=["red","blue","green"];
}
function SubType(){
//
superType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); // "red,blue,green,black"
bar instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
使用這種方式,可以在子類(lèi)型構(gòu)造函數(shù)中向超類(lèi)型構(gòu)造函數(shù)傳遞參數(shù)瑰谜。
function SuperType(name){
this.name=name;
}
function SubType(){
Super.call(this,"Nicholas");
this.age = 29;
}
var instance = new SubType();
alert(instance.name);//"Nicholas"
- 相關(guān)問(wèn)題:
會(huì)出現(xiàn)與構(gòu)造函數(shù)模式相同的問(wèn)題——方法都在構(gòu)造函數(shù)中定義欺冀,函數(shù)復(fù)用就無(wú)從談起了。而且在超類(lèi)型的原型中定義的方法萨脑,對(duì)于子類(lèi)型也是不可見(jiàn)的隐轩。
3.3 組合繼承——JavaScrip最常用的繼承模式
使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承(通過(guò)在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用),通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承(保證每個(gè)實(shí)例都有自己的屬性渤早,而且可以向超類(lèi)的構(gòu)造函數(shù)傳遞參數(shù))职车。
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.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas",29);
instance1.sayName(); //"Nicholas"
instance1.sayAge(); //29
var instance2 = new SubType("Greg",27);
instance2.sayName(); //"Greg"
instance2.sayAge(); //27
3.4 原型式繼承
借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類(lèi)型。
function object(o){
// 臨時(shí)的構(gòu)造函數(shù)
function F(){}
// 將傳入的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型
F.prototype = o;
// 返回新實(shí)例
return new F();
}
var person = {
name:"Nicholas",
friends:["shelby","court","van"]
};
// 以person為原型創(chuàng)建一個(gè)新實(shí)例
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"
person.friends不僅屬于person所有悴灵,而且也會(huì)被anotherPerson军援、yetAnotherPerson共享。
ECMAScript5通過(guò)Object.create()方法規(guī)范化了原型式繼承称勋,這個(gè)方法接受兩個(gè)參數(shù):一個(gè)用作新對(duì)象原型的對(duì)象,另一個(gè)(可選的)為新對(duì)象定義額外屬性的對(duì)象涯竟。只傳入一個(gè)參數(shù)的情況赡鲜,和object()方法相同。
// Object.create()只用一個(gè)參數(shù)
var person = {
name: "Nicholas",
friends: ["shelby","court","van"]
};
var anotherPerson = Object.create(person);
// Object.create()用兩個(gè)參數(shù)
var person = {
name: "Nicholas",
friends: ["shelby","court","van"]
};
var anotherPerson = Object.create(person, {
name:{
value: "Greg"
}
});
alert(anotherPerson.name); //Greg
var yetAnotherPerson = Object.create(person, {
// 為新對(duì)象定義新的friends屬性庐船,該屬性會(huì)覆蓋原型屬性中的friends
friends:{
value: ["1","2","3"]
}
});
alert(yetAnotherPerson.friends); //"1,2,3"
// 但是原型對(duì)象中的friends屬性仍然被共享
alert(person.friends); //"shelby,court,van"
alert(anothorPerson.friends); //"shelby,court,van"
3.5 寄生式繼承
function createAnother(original){
var clone = object(original);
// 不能做到函數(shù)復(fù)用银酬,導(dǎo)致效率降低
// 添加新函數(shù),增強(qiáng)對(duì)象
clone.sayHi = function(){
alert("hi");
}
return clone;
}
var person = {
name: "Nicholas",
friends: ["shelby","court","van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
經(jīng)試驗(yàn)
使用原型式繼承方式出現(xiàn)如下問(wèn)題
function object(o){
function F(){}
F.sayHi = function(){
alert("Hi");
};
F.prototype = o;
return new F();
}
var person = {
name:"Nicholas",
friends:["shelby","court","van"]
};
var anotherPerson = object(person);
anotherPerson.sayHi();
會(huì)輸出錯(cuò)誤 VM6988:18 Uncaught TypeError: anotherPerson.sayHi is not a function
但是這種方式?jīng)]有問(wèn)題
function object(o){
function F(){}
F.prototype = o;
// 返回新實(shí)例
F.prototype.sayHi=function(){
alert("Hi");
}
return new F();
}
var person = {
name:"Nicholas",
friends:["shelby","court","van"]
};
// 以person為原型創(chuàng)建一個(gè)新實(shí)例
var anotherPerson = object(person);
anotherPerson.sayHi(); //"Hi"
function object(o){
function F(){
this.sayHi = function(){
alert("Hi");
}
}
F.prototype = o;
// 返回新實(shí)例
return new F();
}
var person = {
name:"Nicholas",
friends:["shelby","court","van"]
};
// 以person為原型創(chuàng)建一個(gè)新實(shí)例
var anotherPerson = object(person);
anotherPerson.sayHi(); //"Hi"
原因分析
function F(){}
F.sayHi = function(){
alert("Hi");
};
2017.03.15 F.sayHi是定義了“類(lèi)”方法筐钟,因此創(chuàng)建的對(duì)象是沒(méi)有該方法的
3.6 寄生組合式繼承
組合繼承無(wú)論在什么情況下揩瞪,都會(huì)調(diào)用兩次超類(lèi)型構(gòu)造函數(shù):一次是在創(chuàng)建子類(lèi)型原型的時(shí)候,另一次是在子類(lèi)型構(gòu)造函數(shù)內(nèi)部篓冲。
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); //第二次調(diào)用SuperType()
this.age=age;
}
SubType.prototype = new SuperType(); //第一次調(diào)用SuperType()
第一次調(diào)用SuperType構(gòu)造函數(shù)時(shí)李破,SubType.prototype(SubType的原型)會(huì)得到兩個(gè)屬性:name和colors。當(dāng)調(diào)用SubType構(gòu)造函數(shù)時(shí)壹将,又會(huì)調(diào)用一次SuperType構(gòu)造函數(shù)嗤攻,這一次又在新對(duì)象上創(chuàng)建了實(shí)例屬性name和colors。于是這兩個(gè)屬性就屏蔽了原型中的兩個(gè)同名屬性
為了避免創(chuàng)建兩次相同的屬性诽俯,解決方法——寄生組合式繼承妇菱。其基本思想是:不必為了指定子類(lèi)型的原型而調(diào)用超類(lèi)型的構(gòu)造函數(shù),我們所需要的無(wú)非就是超類(lèi)型原型的一個(gè)副本而已暴区。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}
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);
};
上訴例子只調(diào)用了一次SuperType構(gòu)造函數(shù)闯团,因此避免了在SubType.prototype上面創(chuàng)建不必要的、多余的屬性仙粱。寄生組合式繼承是引用類(lèi)型最理想的繼承范式
相比之下房交,第二段程序少了SubType.prototype = new SuperType();這使得SubType.prototype中沒(méi)有了name和colors屬性,實(shí)現(xiàn)了避免了在SubType.prototype上面創(chuàng)建不必要的伐割、多余的屬性的目的涌萤。