繼承分為接口繼承和實(shí)現(xiàn)繼承,但由于接口繼承只繼承方法簽名,而函數(shù)沒(méi)有簽名,所以ECMAScript只支持實(shí)現(xiàn)繼承(實(shí)現(xiàn)繼承繼承實(shí)際的方法)。實(shí)現(xiàn)繼承主要是依靠原型鏈來(lái)實(shí)現(xiàn)的。
一. 原型鏈
1.基本思想:利用原型讓一個(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)部指針。那么如果讓原型對(duì)象等于另一個(gè)類(lèi)型的實(shí)例,結(jié)果會(huì)怎樣呢?顯然,此時(shí)的原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針,相應(yīng)的,另一個(gè)原型中也包含著指向另一個(gè)構(gòu)造函數(shù)的指針。

實(shí)現(xiàn)原型鏈有一種基本模式,其代碼大致如下
function SuperType(){
this.supername=true;
}
SuperType.prototype.getSuperValue=function(){
return this.supername;
};
function SubType(){
this.subname=false;
}
//繼承了SuperType
SubType.prototype=new SuperType();
//添加新方法
SubType.prototype.getSubValue=function(){
return this.subname;
};
var instance=new SubType();
alert(instance.getSuperValue()) //true
-
構(gòu)造函數(shù),實(shí)例,原型之間具體關(guān)系圖及最終結(jié)果的詳細(xì)說(shuō)明
構(gòu)造函數(shù),實(shí)例,原型之間具體關(guān)系圖
(1)instance指向SubType的原型,SubType的原型又指向SuperType的原型。
(2)getSuperValue()方法仍然在SuperType.prototype中,但prototype則位于SubType.prototype中,這是系因?yàn)閜rototype是一個(gè)實(shí)例屬性( 此時(shí)SubType.prototype=new SuperType() ),而getSuperValue()是一個(gè)原型方法。
(3)此時(shí)的instance.constructor指向的是SuperType,這是因?yàn)樵瓉?lái)的SubType.prototype中的constructor被重寫(xiě)了的緣故
-
默認(rèn)的原型
所有引用類(lèi)型默認(rèn)都繼承了Object,而這個(gè)繼承也是通過(guò)原型鏈實(shí)現(xiàn)的。因此,所有默認(rèn)原型都是Object的實(shí)例,Object對(duì)象處于原型鏈的頂端 - 判斷原型和實(shí)例之間的關(guān)系
(1)instanceof
只要用這個(gè)操作符來(lái)測(cè)試實(shí)例與原型鏈中出現(xiàn)過(guò)的構(gòu)造函數(shù),結(jié)果都會(huì)返回rue
var instance=new SubType();
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true
(2)isPrototypeOf()
只要是原型鏈中出現(xiàn)過(guò)的原型,都可以說(shuō)是該原型鏈所派生的實(shí)例的原型,返回true。
var instance=new SubType();
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true
2.添加方法與重寫(xiě)超類(lèi)型中的方法
(1)給原型添加方法的代碼一定要放在替換原型的語(yǔ)句之后,即繼承,再給prototype添加方法。
//繼承了SuperType
SubType.prototype=new SuperType();
//添加新方法
SubType.prototype.getSubValue=function(){
return this.subname;
};
//重寫(xiě)超類(lèi)型中的方法
SubType.prototype.getSuperValue=function(){
return 1;
}
var instance=new SubType();
alert(instance.getSuperValue()); // 1
重寫(xiě)了 getSuperValue()方法后,通過(guò)SubType的實(shí)例調(diào)用這個(gè)方法時(shí),調(diào)用的是這個(gè)重新定義的getSuperValue()方法,返回1,但是通過(guò)SuperType的實(shí)例調(diào)用這個(gè)方法時(shí),調(diào)用的還是原來(lái)的getSuperValue()。
(2)不能用對(duì)象字面量創(chuàng)建原型方法。
如果用對(duì)象字面量創(chuàng)建原型方法,就會(huì)重寫(xiě)原型鏈
//繼承了SuperType
SubType.prototype=new SuperType();
//用對(duì)象字面量添加方法
SubType.prototype={
getSubValue:function(){
return this.subname;
};
someOtherMethod:funtion(){
return "other medthod!";
}
};
var instance=new SubType();
alert(instance.getSuperValue()); //出現(xiàn)error
由于現(xiàn)在的原型包含的是一個(gè)object實(shí)例,而非SuperType的實(shí)例,因此我們?cè)O(shè)想中的原型鏈已經(jīng)被切斷——SubType和SuperType之間已經(jīng)沒(méi)有任何關(guān)系。
3.原型鏈的問(wèn)題
牽一發(fā)而動(dòng)全身 : 原先的實(shí)例屬性變成了現(xiàn)在的原型屬性,只要有一個(gè)A對(duì)象的實(shí)例改動(dòng)繼承過(guò)來(lái)的屬性,就會(huì)影響到所有的A對(duì)象實(shí)例。從下面這個(gè)例子就可以看出
function SuperType(){
this.colors=["red","blue"];
}
function SubType(){}
//繼承了SuperType
SubType.prototype=new SuperType();
//instance_a為第一個(gè)SubType對(duì)象
var instance_a=new SubType();
instance_a.colors.push("green");
alert(instance_a.colors); //red,blue,green
//instance_b為第二個(gè)SubTypee對(duì)象
var instance_b=new SubType();
alert(instance_b.colors); //red,blue,green
當(dāng)SubType通過(guò)原型鏈繼承了SuperType之后,SubType.prototype就變成了SuperType的一個(gè)實(shí)例,因此它也擁有了一個(gè)它自己的colors屬性。SubType的所有實(shí)例都會(huì)共享這一個(gè)colors屬性。
解決方案見(jiàn)下↓↓↓↓↓↓↓↓↓↓↓
二 .借用構(gòu)造函數(shù)
基本思想:在子類(lèi)型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類(lèi)型構(gòu)造函數(shù)。
1.使用apply()和call()方法
function SuperType(){
this.colors=["red","blue"];
}
function SubType(){
//繼承了SuperType
SuperType.call(this);
}
var instance_a=new SubType();
instance_a.colors.push("green");
alert(instance_a.colors); //red,blue,green
var instance_b=new SubType();
alert(instance_b.colors); //red,blue
通過(guò)使用call方法(也可以是apply),我們實(shí)際上是在將來(lái)新建的SubType實(shí)例的環(huán)境下調(diào)用了SuperType構(gòu)造函數(shù)。這樣一來(lái),就會(huì)在新SubType對(duì)象上執(zhí)行SuperType()函數(shù)中定義的所有對(duì)象初始化代碼。因此SubType的每一個(gè)實(shí)例就會(huì)具有自己的colors屬性的副本了。
2.傳遞參數(shù)
相對(duì)于原型鏈,借用構(gòu)造函數(shù)有一個(gè)很大的優(yōu)勢(shì),即可以在子類(lèi)型構(gòu)造函數(shù)中向超類(lèi)型構(gòu)造函數(shù)傳遞參數(shù)。如下:
function SuperType(name){
this.name=name;
}
function SubType(){
//繼承了SuperType,同時(shí)還傳遞了參數(shù)
SuperType.call(this,"Jim");
this.age=18;
}
var instance=new SubType();
alert(instance.name); //Jim
alert(instance.age); //18
3.借用構(gòu)造函數(shù)的問(wèn)題
- 使用構(gòu)造函數(shù)模式在父類(lèi)中定義方法:方法就沒(méi)辦法復(fù)用,詳見(jiàn)《js對(duì)象——?jiǎng)?chuàng)建對(duì)象(1)》之構(gòu)造函數(shù)模式
- 使用原型模式在父類(lèi)中定義方法:使用call無(wú)法繼承父類(lèi)的原型
三 .組合繼承(常用)
組合繼承有時(shí)候也稱(chēng)經(jīng)典繼承,指的是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合在一起。
基本思想:使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。
function SuperType(name){
this.name=name;
this.colors=["red","blue"];
}
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 instance_a=new SubType("Jim",18);
instance_a.colors.push("green");
alert(instance_a.colors) //red,blue,green
instance_a.sayName(); //Jim
instance_a.sayAge(); //18
var instance_b=new SubType("Amy",19);
alert(instance_b.colors); //red,blue
使用instanceof和isPrototypeOf()識(shí)別基于組合繼承創(chuàng)建的對(duì)象
缺點(diǎn):最大的問(wèn)題是無(wú)論什么情況下,都會(huì)調(diào)用兩次超類(lèi)型構(gòu)造函數(shù):一次是在創(chuàng)建子類(lèi)型原型的時(shí)候(SubType.prototype=new SuperType();),另一次是在子類(lèi)型構(gòu)造函數(shù)內(nèi)部(SuperType.call(this,name); //繼承屬性)。
四 .原型式繼承
當(dāng)沒(méi)有必要興師動(dòng)眾的創(chuàng)建構(gòu)造函數(shù),而只想讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持相似的情況下,可以選擇原型式繼承
基本思想:借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類(lèi)型。
1.基本用法
看看下面的例子
var person={
name:"Jim",
friends:["Amy","Tom"]
};
var person_a=object(person); //Jim ,,此處請(qǐng)用Object.create()代替object(),因?yàn)閛bject自ES5后被Object.create取代,所以會(huì)出現(xiàn)錯(cuò)誤。
alert(person_a.name);
person_a.friends.push("Van");
alert(person.friends); //Amy,Tom,Jim
alert(person_a.friends); //Amy,Tom,Jim
注:object自ES5被更加規(guī)范的Object.create所取代,并且Object.create()與object()行為相同,稍后會(huì)講到Object.create
原型式繼承,要求你必須有一個(gè)對(duì)象可以作為另一個(gè)對(duì)象的基礎(chǔ)。找到作為基礎(chǔ)的對(duì)象(可以被修改),然后傳遞給object()函數(shù),再根據(jù)具體需求修改即可。
- object()做了什么呢?
function object(o){
function F(){}
F.prototype=o;
return new F();
}
2.新增的Object.create()
ECMAScript5通過(guò)新增的Object.create()方法規(guī)范了原型式繼承。這個(gè)方法接收兩個(gè)參數(shù),一個(gè)用作新對(duì)象原型的對(duì)象,另一個(gè)為新對(duì)象定義額外的屬性的對(duì)象。在傳入一個(gè)參數(shù)的情況下Object.create()=object()
var person={
name:"Jim",
friends:["Amy","Tom"]
};
var person_a=Object.create(person,{
name:{
value:"Greg",
}
});
alert(person_a.name); //Greg
五. 寄生式繼承
寄生式繼承與寄生構(gòu)造函數(shù)和工廠(chǎng)模式類(lèi)似,即創(chuàng)建一種僅用于封裝繼承過(guò)程的函數(shù),該函數(shù)在內(nèi)部以某種方式來(lái)增強(qiáng)對(duì)象,最后再像是做了所有工作一樣返回對(duì)象。
//用于封裝繼承過(guò)程的函數(shù)createAnother
function createAnother(original){
var clone=Object.create(original); //通過(guò)調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象
clone.sayHi=function(){ //以某種方式來(lái)增強(qiáng)這個(gè)對(duì)象(為對(duì)象添加新方法)
alert("Hi,"+this.name);
};
return clone; //返回這個(gè)對(duì)象
}
//創(chuàng)建基礎(chǔ)的person對(duì)象
var person={
name:"Jim",
friends:["Amy","Tom"]
};
var person_a=createAnother(person);
person_a.sayHi(); // Hi,Jim
在主要考慮對(duì)象 而不是自定義類(lèi)型和夠愛(ài)函數(shù)的情況下,寄生式模式是一種有用的模式
缺點(diǎn):由于不能做到函數(shù)的復(fù)用而降低效率,這一點(diǎn)與構(gòu)造函數(shù)模式相似
六 . 寄生組合式繼承
前面有說(shuō)過(guò),最常用的繼承方式是組合繼承,但組合繼承的缺點(diǎn)是會(huì)調(diào)用兩次超類(lèi)型構(gòu)造函數(shù)。寄生組合式繼承就解決了這一麻煩。
組合繼承兩次調(diào)用超類(lèi)型的地方在:
(1)創(chuàng)建子類(lèi)型原型 SubType.prototype=new SuperType(); : 繼承原型屬性和方法
(2)子類(lèi)型構(gòu)造函數(shù)內(nèi)部 SuperType.call(this); : 繼承實(shí)例屬性
寄生組合式繼承選擇丟棄(1)保留(2),用什么來(lái)繼承原型呢?使用寄生式繼承來(lái)繼承超類(lèi)型的原型。并且繼續(xù)用構(gòu)造函數(shù)來(lái)繼承實(shí)例屬性
1. 寄生組合式繼承的基本模式
function inheritPrototype(subType,superType){
var prototype=Object.create(superType.prototype); //創(chuàng)建對(duì)象
prototype.constructor=subType; //增強(qiáng)對(duì)象
subType.prototype=prototype; //指定對(duì)象
}
這個(gè)函數(shù)接收兩個(gè)參數(shù):子類(lèi)型構(gòu)造函數(shù)和超類(lèi)型構(gòu)造函數(shù)。
在函數(shù)內(nèi)部:
(1)第一步:是建立超類(lèi)型原型副本
(2)大二步:為創(chuàng)建的副本添加constructor屬性,從而彌補(bǔ)因重寫(xiě)原型而失去的默認(rèn)constructor屬性。
(3)第三步:將新創(chuàng)建的對(duì)象賦值給子類(lèi)型的原型。
2.寄生組合式的使用
function inheritPrototype(subType,superType){
var prototype=Object.create(superType.prototype); //創(chuàng)建對(duì)象
prototype.constructor=subType; //增強(qiáng)對(duì)象
subType.prototype=prototype; //指定對(duì)象
}
function SuperType(name){
this.name=name;
this.colors=["red","blue"];
}
SuperType.prototype.sayName=function(){
alert(this.name);
}
//定義SubType對(duì)象
function SubType(name,age){
SuperType.call(this,name); //通過(guò)構(gòu)造函數(shù)繼承屬性
this.age=age;
}
inheritPrototype(SubType,SuperType); //通過(guò)原型鏈的混合形式繼承方法
SubType.prototype.sayAge=function(){
alert(this.age);
}
var sub=new SubType("Jim",18);
sub.sayName(); //Jim
sub.sayAge(); //18
這個(gè)例子的高效率體現(xiàn)在它只調(diào)用了一次SuperType構(gòu)造函數(shù)。開(kāi)發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類(lèi)型最理想的繼承范式。
總結(jié)
(1)原型鏈:JavaScript主要是通過(guò)原型鏈實(shí)現(xiàn)繼承。原型鏈的構(gòu)建是通過(guò)將一個(gè)類(lèi)型的實(shí)例實(shí)例復(fù)制給另一個(gè)構(gòu)造函數(shù)的原型實(shí)現(xiàn)的。這樣子類(lèi)型就可以訪(fǎng)問(wèn)超類(lèi)型的所有屬性和方法。但是原型鏈的問(wèn)題是,子類(lèi)型也可以修改超類(lèi)型的所有屬性和方法。
(2)借用構(gòu)造函數(shù):在子類(lèi)型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類(lèi)型的構(gòu)造函數(shù)。這樣每個(gè)實(shí)例都有自己的屬性,同時(shí)還能保證只使用構(gòu)造函數(shù)模式來(lái)定義類(lèi)型。但是這樣的話(huà),函數(shù)的復(fù)用也就無(wú)從談起了。
(3)組合繼承:使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。缺點(diǎn)是至少調(diào)用了兩次超類(lèi)型。
(4)原型式繼承:可以在不必預(yù)先定義構(gòu)造函數(shù)的情況下實(shí)現(xiàn)繼承。復(fù)制得到的副本還可以得到進(jìn)一步改造。
(5)寄生式繼承:和原型式繼承很相似,但是在函數(shù)內(nèi)部增強(qiáng)了對(duì)象(添加方法等),最后返回對(duì)象
(6)寄生組合式繼承:集寄生式繼承和組合繼承的優(yōu)點(diǎn)于一身,是實(shí)現(xiàn)基于類(lèi)型繼承最有效的方式
---------------------------《JavaScript高級(jí)程序設(shè)計(jì)(第三版)》讀書(shū)筆記-------------------------------
