在JavaScript中,可以使用類別(Class)來實作物件導向程式設計(Object Oriented Programming)。不過,JavaScript中的類別與Java中的有所不同,其對應的定義和使用也不一樣。
JavaScript中類別的定義
在JavaScript中,所有從同一個原型物件(prototype)衍生出來的物件組成了一個類別;也就是說,JavaScript中的類別是一個物件集合的概念,如果兩個物件它們的prototype相同,那麼它們就屬於同一個類別;JavaScript中的類別甚至不需要類別名稱。以下面的程式碼為例:
在上述例子中,物件a和b擁有相同的原型物件(prototype) p,因此a和b屬於同一個類別(雖然這個類別都沒有類別名稱),它們從原型物件p處繼承了值為42的屬性x。
從這個例子可以看到,原型對象的作用就相當於模板,可以由之衍生/創建出多個對象,其地位與Java語言中的類代碼(Class code)相同,是JavaScript中類定義的核心。以下這個範例中的原型物件就呈現出更像類別程式碼的樣子:
在上述例子中,原型物件p定義了一個值為1的property (INCREMENT_BY)和一個名為increment的函數;物件a和b從p這個範本取得了INCREMENT_BY和increment函數。當呼叫物件a或b的increment函數時,JavaScript會試圖取得a或b的INCREMENT_BY值(this.INCREMENT_BY);由於INCREMENT_BY是從p中取得的,因此其值都是1 — 從範本中取得的,值都相同的變量,類似於Java中的靜態類別變數(static variable),因此上面的範例中對INCREMENT_BY變數命名時使用了全大寫字元。
在上面的範例中,所有從模板p處創建出來的物件(屬於同一個類別的這些物件),其屬性和行為都是一模一樣的。但實際上對於同一個類別的不同對象,它們除了擁有類別所定義的屬性/行為以外,往往具有一些自身所特有的屬性與行為。因此,如果需要將prototype這個模板當作類別來使用的話,就必須對每一個從中衍生出來的物件進行一定的自訂:
在這個範例中,從範本p建立出來的物件a和b擁有一個彼此間值不一定相等的變數custom_increment_by,而它們的increment()函數這個行為的最終結果則與custom_increment_by的值相關。一般來說,對新建物件進行客製化的工作往往放在統一的函數中進行:
如此,便透過原型物件p和getIncrementalClassObject()函數完成了一個類別的定義:可以透過呼叫getIncrementalClassObject()函數來取得原型物件都是p的對象,而在呼叫getIncrementalClassObject()函數過程中可以對這些新建對象進行一定的客製化。值得注意的是,此時這個已經定義了的類別還沒有類別名,為了方便描述,姑且稱之為Incremental。
回顧getIncrementalClassObject()函數所做的工作,可以看到從Incremental這個類別中建立新的物件所經歷的過程如下:
1.建立一個空對象,並將其原型對象定義為p。
2.根據不同的參數值,對這個新建的空物件進行自訂。
3.傳回已經定製完成的新物件。
在JavaScript中,可以透過使用Constructor(建構子)來快速地完成類別的定義以及新物件的建立。
JavaScript中的Constructor(建構子)
從上述Incremental類別這個範例可以看到,定義新的類別需要兩部分程式碼:建立原型物件作為範本、建立自訂函數對新物件進行初始化;而從類別中建立新的物件則經歷了三個過程:指定新物件的原型物件、自訂/初始化新物件、傳回這個新物件。在JavaScript中,這一切都可以透過Constructor(建構子)來完成。
JavaScript中的Constructor是一個函數(function),承擔對新物件進行初始化的職責;而這個Constructor函數的prototype則是用來建立新物件。仍以上述Incremental類別為例,用Constructor來重寫程式碼後是這樣的:
var a = new Incremental(0);
var b = new Incremental(1);
console.log(a.increment(7));//8
console.log(b.increment(9));//11
透過new關鍵字,使用Constructor函數來建立新物件這個過程,實際上經歷了以下階段:
建立一個新的空物件。
1.將這個物件的原型物件指向constructor函數的prototype屬性。
2.將這個物件作為this參數,執行constructor函數。
3.這與之前的getIncrementalClassObject()函數中所做的工作是一樣的。
類別名稱
在使用Constructor建立物件時,對應的物件也就有了“類別名稱”,這可以從instanceof操作符的結果上得到驗證: