詳述JavaScript實(shí)現(xiàn)繼承的幾種方式(推薦)
ECMAScript只支持實(shí)現(xiàn)繼承,而且其實(shí)現(xiàn)繼承主要是依靠原型鏈來實(shí)現(xiàn)的。
原型鏈
原型鏈的基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。每一個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個指向原型對象的指針。如果:我們讓原型對象A等于另一個類型B的實(shí)例,那么原型對象A就會有一個指針指向B的原型對象,相應(yīng)的B的原型對象中保存著指向其構(gòu)造函數(shù)的指針。假如B的原型對象又是另一個類型的實(shí)例,那么上述的關(guān)系依舊成立,如此層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條。
實(shí)例以及構(gòu)造函數(shù)和原型之間的關(guān)系圖如下所示:
person.constructor現(xiàn)在指向的是Parent,這是因為Child.prototype指向了Parent的原型,而Parent原型對象的constructor指向Parent。
當(dāng)以讀取模式訪問一個實(shí)例屬性時,首先會在實(shí)例中搜索該屬性,如果沒有找到該屬性,則會繼續(xù)搜索實(shí)例的原型。在通過原型鏈實(shí)現(xiàn)的集成中,搜索過程就會沿著原型鏈繼續(xù)向上,直到搜索到原型鏈的末端。
例如,調(diào)用person.getParentValue()方法,1)搜索實(shí)例;2)搜索Child.prototype;3)搜索Parent.prototype;找到了getParentValue()方法停止。
1、默認(rèn)的原型
前面的例子中展示的原型鏈少了一環(huán),所有引用類型默認(rèn)都繼承了Object,而這個繼承也是通過原型鏈實(shí)現(xiàn)的。因此默認(rèn)的原型都包含一個內(nèi)部指針,指向Object.prototype,這也正是所有自定義類型會繼承toString()、ValueOf()等默認(rèn)方法的根本原因。換句話說Object.prototype就是原型鏈的末端。
2、確定原型和實(shí)例的關(guān)系
通過兩種方式可以確定原型和實(shí)例之間的關(guān)系,第一種是使用instanceOf操作符,第二種是使用isPrototypeOf()方法。
實(shí)例 instanceOf 原型鏈 中出現(xiàn)過的構(gòu)造函數(shù),都會返回true
console.log(person instanceOf Child);//true console.log(person instanceOf Parent);//true console.log(person instanceOf Object);//true isPrototype(),只要是原型鏈中出現(xiàn)過的原型,都可以說是該原型鏈所派生出來的實(shí)例的原型,因此也返回true. console.log(Object.prototype.isPrototypeOf(instance));//true console.log(Parent.prototype.isPrototypeOf(instance));//true console.log(Child.prototype.isPrototypeOf(instance));//true
3、謹(jǐn)慎地定義方法
子類型有時候需要覆蓋超類型中的某個方法,或者需要添加超類型中不存在的莫個方法,注意:給原型添加方法的代碼一定要放在替換原型的語句之后。
當(dāng)通過Child的實(shí)例調(diào)用getParentValue()時,調(diào)用的是這個重新定義過的方法,但是通過Parent的實(shí)例調(diào)用getParentValue()時,調(diào)用的還是原來的方法。
格外需要注意的是:必須要在Parent的實(shí)例替換原型之后,再定義這兩個方法。
還有一點(diǎn)需要特別注意的是:通過原型鏈實(shí)現(xiàn)繼承時,不能使用對象字面量創(chuàng)建原型方法,因為這樣做會重寫原型鏈。
以上代碼剛把Parent的實(shí)例賦值給Child的原型對象,緊接著又將原型替換成一個字面量,替換成字面量之后,Child原型實(shí)際上包含的是一個Object的實(shí)例,而不再是Parent的實(shí)例,因此我們設(shè)想中的原型鏈被切斷.Parent和Child之間沒有任何關(guān)聯(lián)。
4、原型鏈的問題
原型鏈很強(qiáng)大,可以利用它來實(shí)現(xiàn)繼承,但是也有一些問題,主要的問題還是包含引用類型值的原型屬性會被所有實(shí)例共享。因此我們在構(gòu)造函數(shù)中定義實(shí)例屬性。但是在通過原型來實(shí)現(xiàn)繼承時,原型對象其實(shí)變成了另一個類型的實(shí)例。于是原先定義在構(gòu)造函數(shù)中的實(shí)例屬性變成了原型屬性了。
舉例說明如下:
在Parent構(gòu)造函數(shù)中定義了一個friends屬性,該屬性值是一個數(shù)組(引用類型值)。這樣,Parent的每個實(shí)例都會各自包含自己的friends屬性。當(dāng)Child通過原型鏈繼承了Parent之后,Child.prototype也用用了friends屬性——這就好像friends屬性是定義在Child.prototype一樣。這樣Child的所有實(shí)例都會共享這個friends屬性,因此我們對kid1.friends做的修改,在kid2.friends中也會體現(xiàn)出來,顯然,這不是我們想要的。
原型鏈的另一個問題是:在創(chuàng)建子類型的實(shí)例時,不能在不影響所有對象實(shí)例的情況下,給超類型的構(gòu)造函數(shù)傳遞參數(shù)。因此,我們通常很少會單獨(dú)使用原型鏈。
借用構(gòu)造函數(shù)
為了解決原型中包含引用類型值帶來的一些問題,引入了借用構(gòu)造函數(shù)的技術(shù)。這種技術(shù)的基礎(chǔ)思想是:在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)。
Parent.call(this)在新創(chuàng)建的Child實(shí)例的環(huán)境下調(diào)用了Parent構(gòu)造函數(shù)。在新創(chuàng)建的Child實(shí)例環(huán)境下調(diào)用Parent構(gòu)造函數(shù)。這樣,就在新的Child對象上,此處的kid1和kid2對象上執(zhí)行Parent()函數(shù)中定義的對象初始化代碼。這樣,每個Child實(shí)例就都會具有自己的friends屬性的副本了。
借用構(gòu)造函數(shù)的方式可以在子類型的構(gòu)造函數(shù)中向超類型構(gòu)造函數(shù)傳遞參數(shù)。
為了確保子類型的熟悉不會被父類的構(gòu)造函數(shù)重寫,可以在調(diào)用父類構(gòu)造函數(shù)之后,再添加子類型的屬性。
構(gòu)造函數(shù)的問題:
構(gòu)造函數(shù)模式的問題,在于方法都在構(gòu)造函數(shù)中定義,函數(shù)復(fù)用無從談起,因此,借用構(gòu)造函數(shù)的模式也很少單獨(dú)使用。
組合繼承
組合繼承指的是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合在一塊,從而發(fā)揮二者之長。即:使用原型鏈實(shí)現(xiàn)對原型屬性和方法的繼承,而通過借用構(gòu)造函數(shù)來實(shí)現(xiàn)對實(shí)例屬性的繼承。
Person構(gòu)造函數(shù)定義了兩個屬性:name和friends。Person的原型定義了一個方法sayName()。Child構(gòu)造函數(shù)在調(diào)用Parent構(gòu)造函數(shù)時,傳入了name參數(shù),緊接著又定義了自己的屬性age。然后將Person的實(shí)例賦值給Child的原型,然后又在該原型上定義了方法sayAge().這樣,兩個不同的Child實(shí)例既分別擁有自己的屬性,包括引用類型的屬性,又可以使用相同的方法了。
組合繼承避免了原型鏈和構(gòu)造函數(shù)的缺陷,融合了他們的有點(diǎn),成為JavaScript中最常用的繼承模式。而且,instanceOf和isPropertyOf()也能夠識別基于組合繼承創(chuàng)建的對象。
最后,關(guān)于JS對象和繼承都還有幾種模式?jīng)]有寫,或者說,我自己也還未去深刻研究,但是,我想,首先將組合模式應(yīng)用的游刃有余。并且,對于為何選用組合模式,知其然,知其所以然。
關(guān)于JavaScript實(shí)現(xiàn)繼承的幾種方式(推薦),小編就給大家介紹到這里,希望對大家有所幫助!
相關(guān)文章
JavaScript實(shí)現(xiàn)酷炫的鼠標(biāo)拖尾特效
這篇文章主要為大家介紹了通過JavaScript實(shí)現(xiàn)的一個超級好看的鼠標(biāo)拖尾特效,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)JavaScript有一定的幫助,感興趣的可以學(xué)習(xí)一下2021-12-12支付寶小程序從手動埋點(diǎn)到自動埋點(diǎn)的實(shí)現(xiàn)過程
埋點(diǎn)的意思是在你想要的數(shù)據(jù)節(jié)點(diǎn)出進(jìn)行設(shè)置,可以方便進(jìn)行采集,下面這篇文章主要給大家介紹了關(guān)于支付寶小程序從手動埋點(diǎn)到自動埋點(diǎn)的相關(guān)資料,需要的朋友可以參考下2022-03-03layui 實(shí)現(xiàn)加載動畫以及非真實(shí)加載進(jìn)度的方法
今天小編就為大家分享一篇layui 實(shí)現(xiàn)加載動畫以及非真實(shí)加載進(jìn)度的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09JavaScript iframe 實(shí)現(xiàn)多窗口通信實(shí)例詳解
這篇文章主要為大家介紹了JavaScript iframe 實(shí)現(xiàn)多窗口通信實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10js改變img標(biāo)簽的src屬性在IE下沒反應(yīng)的解決方法
在Chrome FF里都能改變成功,但在IE下卻不行,網(wǎng)上搜了半天,大概了解了,這個是IE的一個bug,具體的解決方法如下,有類似問題的朋友可以參考下哈,希望對大家有所幫助2013-07-07