Javascript繼承(原型鏈)


  繼承是OO語言中的一個最為人津津樂道的概念。許多OO語言都支持兩種繼承方式:接口繼承和實現繼承。接口繼承只繼承方法簽名,而實現繼承則繼承實際的方法。由於javascript函數沒有簽名,在ECMAScript中無法實現接口繼承,ECMAScript只支持實現繼承,而且其實現主要依靠原型鏈來實現的。

  1、原型鏈

  ECMAScript中描述了原型鏈的概念,並將原型鏈作為實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。每個構造函數都有一個原型對象,原型對象中包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那么,假如我們讓原型對象等於另一個類型的實例,結果會怎樣呢?顯然,此時的原型對象將包含一個指向另一個原型的指針,相應地,另一個原型也包含着一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的實例,那么上述關系依然成立,如此層層遞進,就構成了實例與原型的鏈條,這就是所謂原型鏈的概念。

  實現原型鏈有一種基本模式,其代碼大致如下。

  以上代碼定義了兩個類型:SuperType和SubType。每個類型分別有一個屬性和方法。它們主要區別是SubType繼承了SuperType,而繼承是通過創建SuperType的實例,並將實例賦給SuperType.prototype實現的。實現的本質是重寫原型對象,代之以一個新類型的實例。換句話說,原來存在於SuperType的實例中的所有方法和屬性,現在也存在於SubType.prototype中了,在確立了繼承關系之后,我們給SubType.prototype添加一個方法,這樣就在繼承了SuperType的屬性和方法的基礎上又添加了一個新方法。

  在通過實現原型鏈實現繼承的情況下,搜索過程就是沿着原型鏈繼續向上,,例如上面例子,調用instance.getSuperValue()會經歷三個搜素步驟:1)搜素實例;2)搜素SubType.prototype;3)搜素SuperType.prototype,最后一步才找到該方法。在找不到屬性或方法的情況下,搜素過程總是一環一環地前行到原型鏈的末端才會停下來。

  1.1別忘記默認的原型

  事實上,前面例子中的原型鏈少了一環,我們知道,所有引用類型都默認繼承了Object,而這個繼承也是通過原型鏈實現的。大家要記住,所有函數的默認原型都是Object的實例,因此默認原型都會包含一個內部指針,指向Object.prototype。這也正是所有自定義類型都繼承toString()、valueOf()等默認方法的根本原因,所以我們上面例子展示的原型鏈中還應該包含另外一個繼承層次。 

  Su幣Type繼承了SuperType,而SuperType繼承了Object。當調用instance.toString()時,實際調用的是保存在Object.prototype中的那個方法。

  1.2確定原型和實例的關系

  可以通過兩種方式來確定原型和實例之間的關系。第一種方式是使用instanceof操作符,只要用這個操作符來測試實例與原型鏈中出現過的構造函數,就會返回true,一下幾行代碼就說明了這一點。

  

  由於原型鏈的關系,我們可以說instance是Object、SuperType、SubType任何一個類型的實例。因此,測試者三個構造函數結果都返回了true。

  第二種方式是使用isProttotypeOf()方法。同樣,只要是原型鏈中出現過的原型,都可以說是該原型鏈所派生的實例原型,因此isProttotypeOf()方法也會返回true,如下所示:

  

  1.3謹慎地定義方法

  子類型如果需要重寫超類型中的某個方法,或者希望添加超類型中不存在的某個方法,不管怎樣,給原型添加方法的代碼一定要放在替換原型語句之后,如下圖所示:

  

  在以上代碼中,方框2個方法的定義,第一個方法getSubValue()被添加到了SubType中。第二個方法getSuperValue()是原型鏈中已經存在的方法,但重寫這個方法將會屏蔽原來的那個方法。換句話說,當通過SubType的實例調用getSuperValue()時,調用的就是重新定義的方法;但通過SuperType的實例調用getSuperValue()時,還會繼續調用原來的那個方法。這里額外注意的是,必須在用SuperType的實例替換原型之后,再定義這2個方法。

  還有一點需要注意的是,通過原型鏈實現繼承時,不能使用對象字面量創建原型方法,因為這樣會重寫原型鏈,如下圖所示:

  

  以上代碼剛剛把SuperType的實例賦給原型,緊接着又將原型替換成一個對象字面量而導致問題。由於現在的原型包含的是一個Object的實例,而非SuperType的實例,因此我們設想中的原型鏈已被切斷——SubType和SuperType之間幾經沒有關系了。

  1.4原型鏈的問題

  原型鏈雖然很強大,可以用它來實現繼承,但它也存在一些問題。其中最主要的問題來自包含引用類型的值原型。包含引用類型的原型屬性會被所有實例共享;而這也正是為什么要在構造函數中,而不是在原型對象中定義屬性的原因。在通過原型來實現繼承時,原型實際上回變成另一個類型的實例。於是,原先的實例屬性也就變成了原型的屬性,如以下代碼:

  

  這個例子中SuperType構造函數中定義了一個colors的屬性,該屬性包含一個數組(引用類型值)。SuperType的每個實例都會有包含自己數組的colors屬性。當SubType通過原型鏈繼承了自己的colors屬性——就專門創建了一個SubType.prototype.colors屬性一樣,結果是SubType的所有實例都共享一個colors屬性。我們通過對instance1.colors的修改能都通過instance2.colors反應出來,就已經證實了這一點。

  原型鏈的第二個問題是:在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數傳遞參數。再加上剛剛討論的由於原型中包含引用類型值所帶來的問題,實踐中很少會單獨使用原型鏈。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM