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