讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 第4章 面向對像編程 >

第4章 面向對像編程

JavaScript的許多特性都借鑒自其他語言,如語法借鑒自Java,函數借鑒自Scheme,原型繼承借鑒自Self,而正則表達式特性借鑒自Perl。

在面向對象的語言(如C#、Java等)中,類是面向對象的基礎,並且具有明顯的層次概念和繼承關係,每個類都有一個超類,從超類中繼承屬性和方法,類還可以進一步被擴展(擴展類稱為子類),這樣就構建了一個多層的、複雜的對象繼承關係。但由於JavaScript是基於對象的弱類型語言,它是以對像為基礎,以函數為模型,以原型為繼承機制的開發模式,因此對於習慣於面向對像開發的用戶來說,需要適應JavaScript語言的靈活性和特殊性。

在大多數編程語言中,繼承都是重要的主題之一。在那些基於類的語言(如Java)中,繼承擁有兩個優勢。類繼承第一個優勢是,它是代碼重用的一種形式,如果一個新的類與一個已存在的類大部分相似,那麼只需要具體說明其不同點即可。代碼重用的模式極為重要,因為它們可以顯著地減少軟件開發的成本。類繼承的另一個優勢是,它包括了一套類型系統的規範。由於程序員無須編寫顯式類型轉換的代碼,所以大大減少了工作量。

建議79:參照Object構造體系分析prototype機制

原型是JavaScript核心特性之一,它通過prototype這個屬性表現出來。在JavaScript中,對像(Object)是沒有原型的,只有構造函數擁有原型,而構造類的實例對象都能夠通過prototype屬性訪問原型對象。

prototype不僅是JavaScript實現和管理繼承的一種機制,更是一種OO(面向對像)的設計思想。從語義角度分析,prototype表示類的原型,就是構造類擁有的原始成員。構造函數的prototype屬性存儲著一個引用對象的指針,該指針指向一個原型對象,它是一個特殊的對象,相當於一個數據集合,內部存儲著構造函數的原始屬性和方法。借助prototype屬性,可以訪問原型對像內部成員。當構造函數實例化後,所有實例對象都可以訪問構造函數的原型成員。如果在原型對像中聲明一個成員,則所有實例對象都可以共享它。

原型具有普通對象的結構,可以將任何普通對像實例設置為原型對象,在默認狀態下原型對像繼承於Object抽像類,由JavaScript原生並依附於每個構造函器上,從而實現構造類的原型屬性和原型方法能夠被所有實例對像繼承。原型對像、原型屬性在JavaScript對像系統中的位置和關係如圖4.1所示。

圖 4.1 原型對像、原型屬性在JavaScript對像系統中的位置和關係

在JavaScript中,對像應該是類(class)和實例(instance)的關係演化。類是對象的模型化,而實例則是類的特徵具體化。類包含很多概念類型,如元類、超類、泛類和類型等。例如:


function Class(type){//構造函數

this.type=type;

}

var instance1=new Class(\"instance1\");//實例對像1

var instance2=new Class(\"instance2\");//實例對像2


使用instanceof運算符可以驗證它們的關係:


alert(instance1 instanceof Class);//true,說明instance1對象是Class構造函數的實例

alert(instance2 instanceof Class);//true,說明instance2對象是Class構造函數的實例


instance1和instance2都是對象,但Class構造函數不是它們唯一的類型,Object也是它們的類型:


alert(instance1 instanceof Object);//true,說明instance1對象也是Object構造函數的實例

alert(instance2 instanceof Object);//true,說明instance2對象也是Object構造函數的實例


Object比Class類型更加抽像,它們之間應該屬於一種繼承關係。


alert(Class instanceof Object);//true,說明Class類是Object對象的實例(或子類)


但instance1和instance2對像卻不是Function構造函數的實例,這說明它們之間沒有直接關係。


alert(instance1 instanceof Function);//false,說明它們不是類型與實例的關係

alert(instance2 instanceof Function);//false,說明它們不是類型與實例的關係


而Object與Function之間的關係就非常微妙,它們都是高度抽像的類型,互為對方的實例。


alert(Object instanceof Function);//true,說明Object對象是Function函數的實例(即子類)

alert(Function instanceof Object);//true,說明Function函數是Object對象的實例(即子類)


Object與Function同時也是兩個不同類型的構造器。下面的代碼能夠很好地顯示它們的差異。


var f=new Function;//實例化Function對像

var o=new Object;//實例化Object對像

alert(f instanceof Function);//true,說明f是Function對象的實例

alert(f instanceof Object);//true,說明f是Object對象的實例

alert(o instanceof Function);//false,說明o不是Function對象的實例

alert(o instanceof Object);//true,說明o是Object對象的實例


instance(對像實例)、Class(類型)、Object(抽像類)和Function(構造類)之間的關係如圖4.2所示。

圖 4.2 類型、原型和對像實例之間的關係

prototype是屬於Function的成員,而prototype對像又是Object的一個實例,構造函數通過點語法訪問prototype,再通過prototype訪問原型對像成員。原型屬性與本地特性之間的關係如圖4.3所示。

圖 4.3 原型屬性與本地特性之間的關係

Object和Function都可以定義原型,Object被視為Function的子類。下面的示例能夠很好地說明Object原型和Function原型的異同,這些原型及其屬性之間的關係如圖4.4所示。


Object.prototype.a=1;//聲明Object的原型屬性a的值為1

Function.prototype.a=2;//聲明Function的原型屬性a的值為2

alert(Object.a);//2,說明屬性a指向Function構造函數的原型

alert(Function.a);//2,說明屬性a指向Function構造函數的原型

var o={}//空的對象直接量

alert(o.a);//1,說明屬性a指向Object構造函數的原型

var f=Object;//引用Object構造函數

alert(f.a);//2,說明屬性a指向Function構造函數的原型

var f1=new Function;//實例化Function對像

alert(f1.a);//2,說明屬性a指向Function構造函數的原型

var o1=new Object;//實例化Object對像

alert(o1.a);//1,說明屬性a指向Object構造函數的原型


圖 4.4 Function、Object、Prototype及其屬性間的關係