讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議101:比較類的構造和析構特性 >

建議101:比較類的構造和析構特性

構造和析構是創建和銷毀對象的過程,它們是對像生命週期中的起點和終點,是最重要的環節。當一個對像誕生時,構造函數負責創建並初始化對象的內部環境,包括分配內存、創建內部對像和打開相關的外部資源等。析構函數負責關閉資源、釋放內部的對象和已分配的內存。

在面向對象的編程中,構造和析構是類的兩個重要特性。構造函數將在對像產生時調用,析構函數將在對像銷毀時調用。調用的過程和實現方法由編譯器完成,我們只需要記住它們調用的時間,因為它們的調用是自動完成的,不需要人工控制。

(1)構造函數

在JavaScript中,被new運算符調用的函數就是構造函數。構造函數被new運算符計算後,將返回實例對象,也就是所謂的對象初始化,即對象的誕生。調用構造函數的過程也是類實例化的過程。

如果構造函數有返回值,並且返回值是引用類型的,那麼經過new運算符計算後,返回的不再是構造函數自身對應的實例對象,而是構造函數包含的返回值(即引用類型值)。


function F(x,y){

this.x=x;

this.y=y;

return;

}

var f=new F(1,2);

alert(f.constructor==F);//false,說明F不再是f的構造函數


在上面的示例中,返回值是一個空的數組,而不再是實例對象。原來構造函數的返回值覆蓋了new運算符的運算結果,此時如果調用f的constructor屬性,那麼返回值是:


function Array{//被封閉的Array核心結構

[native code]

}


上面示例說明返回值是Array的實例,使用下面的代碼可以檢測出來:


alert(f.constructor==Array);//true,說明Array是f的構造函數


利用call和apply方法可以實現動態構造。例如,在下面這個示例中,構造函數A、B和C相互之間通過call方法關聯在一起,當構造對像c時,將調用構造函數C,而在執行構造函數C時,會先調用構造函數B。在調用構造函數B之前,會自動調用構造函數C,從而實現動態構造對象的效果。這種多個構造函數相互關聯在一起的情況稱為多重構造。


function A(x){

this.x=x||0;

}

function B(x){

A.call(this,x);

this.a=[x];

}

function C(x){

B.call(this,x);

this.y=function{

return this.x;

}

}

var c=new C(3);

alert(c.y);//3


根據動態構造特性可以設計類的多態處理:


function F(x,y){//多態類型

function A(x,y){

this.add=function{

return x+\"\"+y;

}

}

function B(x,y){

this.add=function{

return x+y;

}

}

if(typeof x==\"string\"||typeof y==\"string\"){

A.call(this,x,y);

}

else{

B.call(this,x,y);

}

}

var f1=new F(3,4);

alert(f1.add);//調用對像方法add,返回數值7

var f2=new F(\"3\",\"4\");//實例化類F,傳遞字符串

alert(f2.add);//調用對像方法add,返回字符串34


(2)析構函數

析構是銷毀對象的過程。由於JavaScript能夠自動回收垃圾,不需要人工清除,所以當對像使用完畢時,JavaScript會調用對像回收程序來銷毀內存中的對象,這個回收程序相當於一個析構函數。從文法角度來分析,JavaScript是不支持析構語法的。當然,我們也可以主動定義析構函數對對像進行清理。例如,先定義一個析構函數,該函數中包含一個析構方法,把該方法繼承給任意對象,就可以調用它清除對像內部所有成員了。


function D{//析構函數

}

D.prototype={

d:function{//析構方法

for(var i in this){

if(this[i]instanceof D){

this[i].d;

}

this[i]=null;//清除成員

}

}

}

function F{

this.x=1;

this.y=function{

alert(2);

}

}

F.prototype=new D;//綁定析構函數,繼承析構方法

var f=new F;//實例化試驗函數

f.d;//調用析構方法

alert(f.x);//null,說明屬性已經被註銷

f.y//編譯錯誤,說明方法已不存在


構造和析構有一個順序問題。在其他強類型語言中,構造是從基類開始按繼承的層次順序進行的,析構的時候順序正好相反。這樣處理是因為子類可能在構造函數中使用父類的成員變量,如果父類還沒有創建,那麼就會有問題。而在析構的時候,如果父類先析構,也會出現這樣的問題。JavaScript對此沒有嚴格的要求,但它遵循從下到上的順序進行構造,而析構則沒有這方面的要求,只要對像沒有成員引用或對像引用即可進行析構。