JavaScript中的類繼承(轉載)

JavaScript中的類繼承

Douglas Crockford
www.crockford.com

翻譯 ShiningRay

www.nirvanastudio.org

 

And you think you're so clever and classless and free

  • John Lennon

JavaScript 一種沒有類的,面向對象的語言,它使用原型繼承來代替類繼承。這個可能對受過傳統的面嚮對象語言(如C++Java)訓練的程序員來說有點迷惑。JavaScript的原型繼承比類繼承有更強大的表現力,現在就讓我們來看看。i

Java

JavaScript

Strongly-typed

Loosely-typed

Static

Dynamic

Classical

Prototypal

Classes

Functions

Constructors

Functions

Methods

Functions

但首先,爲什麼我們如此關心繼承呢?主要有兩個原因。第一個是類型有利。我們希望語言系統可以自動進行類似類型引用的轉換cast。小類型安全可以從一個要求程序顯示地轉換對象引用的類型系統中獲得。這是強類型語言最關鍵的要點,但是這對像JavaScript這樣的弱類型語言是無關的,JavaScript中的類引用無須強制轉換。

第二個原因是爲了代碼的服用。在程序中常常會發現很多對象都會實現同一些方法。類讓建立單一的一個定義集中建立對象成爲可能。在對象中包含其他對象也包含的對象也是很常見的,但是區別僅僅是一小部分方法的添加或者修改。類繼承對這個十分有用,但原型繼承甚至更有用。

要展示這一點,我們要介紹一個小小的“甜點”可以主我們像一個常規的類語言一樣寫代碼。我們然後會展示一些在類語言中沒有的有用的模式。最後,我們會就會解釋這些“甜點”。

類繼承

首先,我們建立一個Parenizor 類,它有成員 value getset方法,還有一個會將value包裝在括號內的toString方法。

    



    
    



    



    

這個語法可能沒什麼用,但它很容易看出其中類的形式。method 方法接受一個方法名和一個函數,並把它們放入類中作爲公共方法。T

現在我們可以寫成


正如期望的那樣,myString "(0)"

現在我們要建立另一個繼承自Parenizor 類,它基本上是一樣的除了 toString 方法將會產生"-0-" 如果 value 是零或者空。

    





    
        
    
    

The inherits 方法類似於Javaextends uber 方法類似於Javasuper 。它令一個方法調用父類的方法(更改了名稱是爲了避免和保留字衝突).

我們可以寫成這樣


這次, myString "-0-".

JavaScript 並沒有類,但我們可以編程達到這個目的。

多繼承

通過操作一個函數的prototype 對象,我們可以實現多繼承。混合多繼承難以實現而且可能會遭到名稱衝突的危險。我們可以在JavaScript中實現混合多繼承,但這個例子我們將使用一個較規範的形式稱爲瑞士繼承 Swiss Inheritance.

假設有一個NumberValue 類有一個setValue 方法用來檢查 value 是不是在一個指定範圍內的一個數,並在適當的時候拋出異常。我們只要它的 setValue setRange 方法給我們的 ZParenizor 。我們當然不想要它的 toString 方法。這樣,我們寫到:


這個將僅僅添加需要的方法。

寄生繼承

這是另一個書寫 ZParenizor 類的方法。並不從 Parenizor 繼承,而是寫了一個調用了Parenizor 構造器的構造器,並對結果修改最後返回這個結果。這個構造器添加的是特權方法而非公共方法。

    
    
        
            
        
        
    
    

類繼承是一種“是……”的關係,而寄生繼承是一個關於“原是……而現在是……”的關係。構造器在對象的構造中扮演了大量的角色。注意 uber (代替 super 關鍵字)對特權方法仍有效。

類擴展

JavaScript的動態性讓我們可以對一個已有的類添加或替換方法。我們可以在任何時候調用方法。我們可以隨時地擴展一個類。繼承不是這個方式。所以我們把這種情況稱爲“類擴展”來避免和Javaextends ──也叫擴展,但不是一回事──相混淆。

對象擴展

在靜態面嚮對象語言中,如果你想要一個對象和另一個對象有所區別,你必須新建立一個類。但在JavaScript中,你可以向單獨的對象添加方法而不用新建類。這會有巨大的能量因爲你就可以書寫儘量少的類,類也可以寫得更簡單。想想JavaScript的對象就像哈希表一樣。你可以在任何時候添加新的值。如果這個值是一個函數,那他就會成爲一個方法。

這樣在上面的例子中,我完全不需要 ZParenizor 類。我只要簡單修改一下我的實例就行了。


    
        
    
    

我們給 myParenizor 實例添加了一個 toString 方法而沒有使用任何繼承。我們可以演化單獨的實例因爲這個語言是無類型的。

小甜點

要讓上面的例子運行起來,我寫了四個“甜點”方法。首先,method 方法,可以把一個實例方法添加到一個類中。

    
    

這個將會添加一個公共方法到 Function.prototype 中,這樣通過類擴展所有的函數都可以用它了。它要一個名稱和一個函數作爲參數。

它返回 this 。當我寫一個沒有返回值的方法時,我通常都會讓它返回 this 。這樣可以形成鏈式語句。

下面是 inherits 方法,它會指出一個類是繼承自另一個類的。它必須在兩個類都定義完了之後才能定義,但要在方法繼承之前調用。

    

    
        
        
            
                
                
            
            
        
            
            
                
            
        
        
        
        
        
    
    

再來,我們擴展 Function 類。我們加入一個 parent 類的實例並將它做爲新的 prototype 。我們也必須修正 constructor 字段,同時我們加入 uber 方法。

uber 方法將會在自己的prototype 查找某個方法。這個是寄生繼承或類擴展的一種情況。如果我們是類繼承,那麼我們要找到 parentprototype 中的函數。return 語句調用了函數的apply方法來調用該函數,同時顯示地設置 this並傳遞參數。參數(如果有的話)可以從arguments 數組中獲得。不幸的是, arguments 數組並不是一個真正的數組,所以我們又要用到 apply 來調用數組中的 slice 方法。

最後,swiss 方法

    
        
        
    
    

The swiss 方法對每個參數進行循環。每個名稱,它都將 parent的原型中的成員複製下來到新的類的 prototype 中。

總結

JavaScript可以像類語言那樣使用,但它也有一種十分獨特的表現層次。我們已經看過了類繼承、瑞士繼承、寄生繼承、類擴展和對象擴展。這一等系列代碼複用的模式都能來自這個一直被認爲是很小、很簡單的JavaScript語言。

類對象屬於“硬的”。給一個“硬的”對象添加成員的唯一的方法是建立一個新的類。在JavaScript中,對象是“軟的”。要給一個“軟”對象添加成員只要簡單的賦值就行了。

因爲JavaScript中的類是這樣地靈活,你可能會還想到更復雜的類繼承。但深度繼承並不合適。淺繼承則較有效而且更易表達。

轉自: http://shiningray.cnblogs.com/shiningray/articles/143005.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章