讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議91:推薦使用構造函數原型模式定義類 >

建議91:推薦使用構造函數原型模式定義類

JavaScript中定義類型的方式有多種,這也形成了不同的類型模式。

(1)工廠模式

工廠模式是指通過函數把一個類型實例包裝起來,這樣可以通過調用函數來實現類型的實例化。


function wrap(title,pages){

var book=new Object;

book.title=title;

book.pages=pages;

book.what=function{

alert(this.title+this.pages)

}

return book;//初始化後的對象

}


也可以對該模式進行優化,進而消除重複創建相同函數的弊端,節約大量資源。


what=function{

alert(this.title+this.pages)

}

function wrap(title,pages){

var book=new Object;

book.title=title;

book.pages=pages;

book.what=what;

return book;

}


工廠模式只是一種偽裝的構造函數,不推薦使用。

(2)構造函數模式

在JavaScript中,構造函數具有如下特性:

❑構造函數使用new運算符進行調用。

❑在構造函數內部,this關鍵字指代當前實例對象。

❑在構造函數內部,必須通過點運算符來聲明和引用成員。構造函數的結構體內可以包含一般函數的執行語句。

例如,對於下面這個構造函數Box,傳遞給Box的是一個新創建的空對象,該對象是高度抽像但未知的,通過this關鍵字來代稱,this的值就是這個新創建的空對像引用。當使用new運算符實例化構造函數時,可以通過傳遞參數來初始化這個對象的屬性值。


function Box(w,h){//構造函數

this.w=w;

this.h=h;

}

var box1=new Box(4,5);//實例並初始化構造函數


由於每一個構造函數代表一種類型,為了與普通函數進行區分,函數名應該很直觀,並且首字母要大寫(非強制的)。如果構造函數返回對象,那麼被返回的對象將覆蓋this的值。例如:


function Box(w,h){//構造函數

this.w=w;

this.h=h;

return this;

}


(3)原型模式

先聲明一個構造函數,並且利用構造函數的prototype屬性為該構造函數定義原型屬性title和pages,以及原型方法what。構造函數的原型成員將會被所有實例對像繼承,這樣當使用new運算符實例化對像時,所有對象都擁有原型屬性中定義的成員。


function Book{//空類

}

Book.prototype.title=\"Javascript設計方法\";

Book.prototype.pages=200;

Book.prototype.what=function{

alert(this.title+this.pages);

};


從語義的角度分析,通過原型繼承的方式,實現了在前面兩種模式中將對象與其方法分離的設計思想。使用instanceof運算符能夠方便地檢測對像實例的類型。原型模式存在以下兩個問題:

❑由於構造函數已被事先聲明,而原型屬性在類結構聲明之後才被定義,因此無法通過構造函數參數向原型屬性動態傳遞值。這樣所帶來的後果:由該類實例化的所有對象都是一個「模樣」,沒有「個性」。如果改變原型屬性值,那麼所有實例都受到干擾。這是非常嚴重的問題,如果無法解決,該項研究將無果而終。

❑當原型屬性的值為引用類型數據時,如果在一個對像實例中修改該屬性值,將會影響所有的實例。由於原型屬性x的值為一個引用類型數據,因此所有對象實例的屬性x的值都是指向該對象的引用指針。一旦某個對象的屬性值被改動,其他實例對象的屬性值也會隨著發生變化。

(4)構造函數原型模式

構造函數原型模式是建立在原型模式基礎上的一種混合設計模式,將構造函數模式與原型模式混合使用。對於可能會相互影響且希望動態傳遞參數的屬性,將其拆分出來使用構造函數模式進行設計。而對於不需要個性、希望共享,並且又不會相互影響的方法或屬性,單獨使用原型模式來設計。


function Book(title,pages){//構造函數模式設計

this.title=title;

this.pages=pages;

}

Book.prototype.what=function{//原型模式設計

alert(this.title+this.pages);

};


在混合使用構造函數與原型模式時,可以不使用構造函數來定義對象的所有非函數屬性(即對像屬性),而使用原型模式來定義對象的函數屬性(即對像方法)。這樣所有方法都只創建一次,而每個對象都能夠根據需要自定義屬性值。這種混合型模式成為ECMAScript定義類的推薦標準,這也是使用最廣的一種設計模式,它具有前面3種設計模式的所有優點,而且去除了它們的副作用。

遵循面向對象的設計原則,類的所有成員都應該封裝在類結構體內,因此可以進一步優化構造函數原型模式,從而產生動態原型模式。優化的思路:使用條件結構封裝該原型方法,判斷原型方法是否存在,如果存在,則不再創建該方法,否則就創建該方法。


function Book(title,pages){

this.title=title;

this.pages=pages;

if(typeof Book.isLock==\"undefined\"){//創建原型方法的鎖,如果不存在該方法則創建

Book.prototype.what=function{

alert(this.title+this.pages);

};

Book.isLock=true;//創建原型方法後,把鎖鎖上,避免重複創建

}

}


「typeof Book.isLock」表達式能夠檢測該屬性值的類型,如果返回undefined字符串,則表示不存在該屬性值,說明還沒有創建原型方法,允許進入分支結構創建原型方法,並設置該屬性的值為true,這樣它的類型返回值就是boolean字符串。

動態原型模式與構造函數原型模式在性能上是等價的,不過目前使用最廣泛的是構造函數原型混合模式。