我是誰(shuí),我來(lái)自哪,我是誰(shuí)的誰(shuí)
想必大家一定在學(xué)習(xí)或者開(kāi)發(fā)過(guò)程常常被JS獨(dú)有的原型繼承撥過(guò)不少腦弦吧,為何不迎問(wèn)題而上,直面解決這些謎團(tuán),今天就來(lái)總結(jié)一下Js的認(rèn)親問(wèn)題...
為開(kāi)發(fā)需求選擇一種合適的繼承模式可以提高代碼的可讀性和便于排錯(cuò),從而提高我們的開(kāi)發(fā)效率,因而今天就來(lái)總結(jié)一下ES5之前的繼承模式,ES6之后再進(jìn)行補(bǔ)充或者另外討論
原型鏈模式-----------------------------------------------所有繼承模式的基礎(chǔ)
關(guān)鍵詞: 構(gòu)造函數(shù) 原型對(duì)象 實(shí)例對(duì)象 原型對(duì)象為另一類(lèi)型的實(shí)例
function SuperType(){ //超類(lèi)構(gòu)造函數(shù) this.property = true; } SuperType .prototype.getSuperValue = function(){ //定義超類(lèi)的原型方法 return this.property; }; function SubType(){ //父類(lèi)的構(gòu)造函數(shù) this.subproperty = false; } SubType.prototype = new SuperType(); //重寫(xiě)父類(lèi)的原型對(duì)象,繼承超類(lèi) SubType.prototype.getSubValue = function(){ //定義父類(lèi)獨(dú)有的原型方法 return this. subproperty; }; var instance = new SubType(); //實(shí)例化子類(lèi)對(duì)象 alert(instance.getSuperValue()); //true //調(diào)用超類(lèi)的原型方法 }
原型鏈的問(wèn)題:
- 超類(lèi)的實(shí)例屬性變成了子類(lèi)的原型屬性,無(wú)法解決引用類(lèi)型值被所有實(shí)例共享的問(wèn)題.
- 創(chuàng)建子類(lèi)型實(shí)例時(shí)無(wú)法向超類(lèi)型的構(gòu)造函數(shù)中傳遞參數(shù)
以上問(wèn)題導(dǎo)致很少單獨(dú)使用原型鏈.
借用構(gòu)造函數(shù)-------------------------------------------借用超類(lèi)構(gòu)造函數(shù)
關(guān)鍵詞:借用超類(lèi)構(gòu)造函數(shù)
function SuperType(){ this.name = name; } function SubType(){ SuperType.call(this,"LiAo"); this.age = 22; } var instance = new SubType(); alert(instance.name); //"Nicholas" alert(instance.age); // 22
借用構(gòu)造函數(shù)的問(wèn)題
- 與構(gòu)造函數(shù)模式一樣,無(wú)法做到函數(shù)復(fù)用.
- 另外此時(shí)沒(méi)有使用原型鏈,所以子類(lèi)無(wú)法調(diào)用超類(lèi)的原型方法
組合繼承----------------------------------------結(jié)合借用構(gòu)造函數(shù)和原型鏈
關(guān)鍵詞:
- 借用構(gòu)造函數(shù)負(fù)責(zé)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承
- 原型鏈負(fù)責(zé)實(shí)現(xiàn)對(duì)原型屬性和方法的繼承(復(fù)用)
`function SuperType(name){
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){ //定義父類(lèi)的原型方法
alert(this.name);
};
function SubType(name,age){
SuperType.call(this,name); //通過(guò)借用構(gòu)造函數(shù)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承
this.age = age; //子類(lèi)自身的實(shí)例屬性
}
SubType.prototype = new SuperType(); //通過(guò)原型鏈繼承超類(lèi)
SubType.prototype.constructor = SubType; //恢復(fù)constructor的指向
SubType.prototype.sayAge = function(){ //定義父類(lèi)獨(dú)有的原型方法
alert(this.age);
};
var instance = new SubType("LiAo",22);
`
組合繼承模式的優(yōu)點(diǎn)
- 主要融合了原型鏈和借用構(gòu)造函數(shù)模式的優(yōu)點(diǎn)
- 可以用instanceof 和isPrototypeOf()來(lái)識(shí)別創(chuàng)建的對(duì)象類(lèi)型
組合繼承模式的問(wèn)題 - 無(wú)論在什么情況下,都會(huì)調(diào)用兩次超類(lèi)型構(gòu)造函數(shù),一次是在子類(lèi)型實(shí)例化的時(shí)候,第二次是在借用構(gòu)造函數(shù)的時(shí)候,導(dǎo)致父類(lèi)的原型對(duì)象會(huì)出現(xiàn)超類(lèi)的實(shí)例屬性或方法,解決此問(wèn)題的辦法先留個(gè)懸念....
原型式繼承-------------------------------------------------------敷衍式代孕
關(guān)鍵詞:基于已有對(duì)象創(chuàng)建新對(duì)象 對(duì)象關(guān)聯(lián) 生下就撤
工具函數(shù):
function object(o){ function F(){}; //創(chuàng)建臨時(shí)類(lèi)型 F.prototype = o; //讓臨時(shí)類(lèi)型的原型對(duì)象指向傳入的對(duì)象(繼承) return new F(); //返回這個(gè)臨時(shí)類(lèi)型的實(shí)例(返回后就撤!) }
其實(shí)仔細(xì)觀察這個(gè)函數(shù),會(huì)發(fā)現(xiàn)臨時(shí)類(lèi)型F是起著代孕的作用,受人委托替?zhèn)魅氲膵寢寣?duì)象生了個(gè)寶寶,生完后孩子還沒(méi)睜眼就悄然離開(kāi),所以寶寶只認(rèn)當(dāng)前的媽媽,有著媽媽的各種特征.
用法:
var mom = { name = Lily, hairColor: "black", skin:"white" } var baby = object(mom); baby.name = "Jack"; alert(baby.name) ; //"Jack" alert(baby.skin); //"white"
ES5規(guī)范通過(guò)Object.create()方法規(guī)范化原型式繼承
該函數(shù)接收兩個(gè)參數(shù):
①用作新對(duì)象原型的對(duì)象
②為新對(duì)象定義額外屬性的對(duì)象(可選),格式與Object.defineProperties相同,每個(gè)屬性都是通過(guò)自己的描述符定義的.
該函數(shù)的缺點(diǎn):只能定義額外屬性,不能定義額外方法
原型式繼承的優(yōu)點(diǎn)
- 拋棄了構(gòu)造函數(shù),而是把繼承簡(jiǎn)化為對(duì)象之間的關(guān)聯(lián)
- 適用于想讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持類(lèi)似的情況下使用
原型式繼承的缺點(diǎn)
- 通過(guò)原型式繼承創(chuàng)建的對(duì)象是空對(duì)象,只不過(guò)是關(guān)聯(lián)了另一個(gè)對(duì)象,但是在很多時(shí)候,我們需要對(duì)新對(duì)象進(jìn)行加強(qiáng),而這時(shí)候雖然可以在新對(duì)象增添特有的屬性和方法,但是當(dāng)我們需要很多個(gè)這樣的對(duì)象的時(shí)候,一個(gè)個(gè)去增添,就顯得很麻煩和難看,于是下面要將的寄生式繼承可以幫到我們
寄生式繼承----------------------------------------------------負(fù)責(zé)式代孕
關(guān)鍵詞: 增強(qiáng)對(duì)象 生完培養(yǎng)一會(huì)再撤
function createBaby(mom){ var baby = object(mom); baby.sayHi = function(){ alert("hi!); } return baby; }
看完你大概懂了吧,寄生式繼承類(lèi)似原型式繼承,只不過(guò)在內(nèi)部對(duì)新對(duì)象進(jìn)行增強(qiáng),這樣通過(guò)它產(chǎn)生的對(duì)象都是增強(qiáng)式的. 就好像一個(gè)負(fù)責(zé)的代孕媽媽,生完之后還要教會(huì)孩子一些技能,等到親媽來(lái)了再離開(kāi),不過(guò)戲劇化的是,這個(gè)孩子還是只認(rèn)親媽(臥槽)
寄生式繼承的優(yōu)點(diǎn)
- 解決了上面所提到的問(wèn)題,可以快速新建一批增強(qiáng)式的對(duì)象.
寄生組合式繼承-----------------------------最完美的繼承方式(非最優(yōu)雅)
關(guān)鍵詞:只要超類(lèi)構(gòu)造函數(shù)的原型對(duì)象,不要它本身
相信你還記得上面留下的懸念吧,組合式繼承會(huì)二次調(diào)用超類(lèi)構(gòu)造函數(shù),寄生組合式繼承就是為了解決這個(gè)問(wèn)題的,其配合一個(gè)工具函數(shù)使用:
function inheritPrototype(subType,superType){ var prototype = object(superType.prototype); //創(chuàng)建一個(gè)新對(duì)象,關(guān)聯(lián)超類(lèi)構(gòu)造函數(shù)的原型對(duì)象,不要構(gòu)造函數(shù) prototype.constructor = subType; //為新對(duì)象創(chuàng)建一個(gè)constructor指針并正確指向父類(lèi)構(gòu)造函數(shù) subType.prototype = prototype; //重寫(xiě)父類(lèi)構(gòu)造函數(shù)的原型對(duì)象為新對(duì)象 } 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); };
雖然寄生組合式繼承是引用類(lèi)型最理想的繼承范式,但是我還是覺(jué)得代碼還是不夠優(yōu)雅,如果要數(shù)優(yōu)雅程度的話,那就離不開(kāi)ES6這塊巨型語(yǔ)法糖了
上面介紹了的繼承模式有:
原型鏈
借用構(gòu)造函數(shù)
組合繼承
原型式繼承
寄生式繼承
-
寄生組合式繼承
其中原型鏈和借用構(gòu)造函數(shù)模式都有自己比較大的問(wèn)題,組合繼承把兩者的優(yōu)點(diǎn)融合在一起了,因此組合繼承比較常見(jiàn),但是組合繼承也有自己的問(wèn)題,而這個(gè)問(wèn)題在寄生組合式繼承中得到解決,寄生組合式繼承代碼不是那么的優(yōu)雅,但是是最理想的方式.
原型式繼承和寄生式繼承是一對(duì)兒,他們都拋棄了構(gòu)造函數(shù),而選擇一種新的思維來(lái)進(jìn)行繼承------對(duì)象關(guān)聯(lián),這種方式代碼最優(yōu)雅.原型式新建立的對(duì)象是一個(gè)空對(duì)象,寄生式新建的對(duì)象則是一個(gè)增強(qiáng)的對(duì)象(用于大量生產(chǎn)).個(gè)人來(lái)說(shuō),原型式繼承和寄生式繼承是我比較順眼的模式 其他模式感覺(jué)都是為了使js這么語(yǔ)言向傳統(tǒng)的類(lèi)繼承靠近的 其實(shí)js的本質(zhì)就是對(duì)象關(guān)聯(lián),而原型式繼承和寄生式繼承正體現(xiàn)了這一點(diǎn) 這兩種繼承各有各的適合情景,原型式繼承適合生產(chǎn)少量的對(duì)象后自己再進(jìn)行增強(qiáng) 寄生式繼承適合生產(chǎn)大量的有類(lèi)似功能的對(duì)象.
下面是我改寫(xiě)寄生式繼承的一個(gè)工具函數(shù)
function createAndConnect(original,propertyObject){ var clone = Object.create(original); if(typeof propertyObject == "object"&&propertyObject!=null){ for(var key in propertyObject){ clone[key] = propertyObject[key]; } } return clone; }
多增添了一個(gè)用于加強(qiáng)對(duì)象的參數(shù),就不用每次都得在內(nèi)部加強(qiáng)了,而且使用方式顯得相對(duì)優(yōu)雅一點(diǎn),如:
var chinese ={ country: "China", language:"Chinese", getLanguage: function(){ console.log("我的母語(yǔ)是"+this.language); }, skin: "yellow", aveHeight:1.71, face:["nose","eyes","mouse"] } var chineseStudent =createAndConnect(chinese,{ name:"LiAo", //獨(dú)有屬性 height:0.1, //獨(dú)有屬性 score: 59, //獨(dú)有屬性 secondLanguage:"English", //獨(dú)有屬性 skin:"a little yellow", //重寫(xiě)屬性 getSecondLanguage:function(){ console.log("我的第二門(mén)語(yǔ)言是"+this.secondLanguage); } }) console.log(chineseStudent.name); // "LiAo" console.log(chineseStudent.skin); // "a little yellow" console.log(chineseStudent.face); //["nose","eyes","mouse"] chineseStudent.getLanguage(); //我的母語(yǔ)是Chinese chineseStudent.getSecondLanguage(); //我的第二門(mén)語(yǔ)言是English
最后的話
在createAndConnect函數(shù)(意思是創(chuàng)建和鏈接)的第二個(gè)參數(shù)中,你可以指定新對(duì)象自己的屬性和方法,也可以"重寫(xiě)"原型對(duì)象的屬性和方法,我認(rèn)為這個(gè)才是js原型繼承的精髓即對(duì)象關(guān)聯(lián).同時(shí)我也倡導(dǎo)大家用對(duì)象關(guān)聯(lián)的思維去開(kāi)發(fā)js應(yīng)用!