構造和析構是創建和銷毀對象的過程,它們是對像生命週期中的起點和終點,是最重要的環節。當一個對像誕生時,構造函數負責創建並初始化對象的內部環境,包括分配內存、創建內部對像和打開相關的外部資源等。析構函數負責關閉資源、釋放內部的對象和已分配的內存。
在面向對象的編程中,構造和析構是類的兩個重要特性。構造函數將在對像產生時調用,析構函數將在對像銷毀時調用。調用的過程和實現方法由編譯器完成,我們只需要記住它們調用的時間,因為它們的調用是自動完成的,不需要人工控制。
(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對此沒有嚴格的要求,但它遵循從下到上的順序進行構造,而析構則沒有這方面的要求,只要對像沒有成員引用或對像引用即可進行析構。