引言:Java是基于對(duì)象的,為什么這么說(shuō)呢。作為腳本語(yǔ)言,操控DHTML等網(wǎng)頁(yè)內(nèi)部元素不應(yīng)該太復(fù)雜,為了簡(jiǎn)化程序設(shè)計(jì),Java中創(chuàng)建了document等重要對(duì)象,這些對(duì)象是Java的固有對(duì)象,用起來(lái)十分方便。document對(duì)象不妨簡(jiǎn)單的理解為用戶界面對(duì)象,使用Java編程時(shí)常常圍繞著它,而不需要煩鎖的定義、創(chuàng)建等過(guò)程。所以Java更多考慮對(duì)現(xiàn)有對(duì)象的控制,而對(duì)對(duì)象的創(chuàng)建擴(kuò)展的能力較差。即便如此,Java仍可創(chuàng)建對(duì)象,只是沒(méi)有C++那么完整。
一、本文名詞解釋?zhuān)鹤兞、串?duì)象、對(duì)象變量(對(duì)象)、原型、原型對(duì)象、實(shí)例、函數(shù)對(duì)象(構(gòu)造函數(shù)、構(gòu)造器)。
為了形象的說(shuō)明問(wèn)題,引入名詞“構(gòu)造器”,構(gòu)造器指函數(shù)對(duì)象,在JS中構(gòu)造器也是構(gòu)造函數(shù)。
對(duì)象變量常稱(chēng)為對(duì)象。當(dāng)變量是個(gè)對(duì)象時(shí),實(shí)際上它是一個(gè)指針,該指針指向?qū)ο蟮膶?shí)際內(nèi)存位置。對(duì)象指針可以復(fù)制,內(nèi)存對(duì)象不可復(fù)制。要實(shí)現(xiàn)真正的復(fù)制是一件很麻煩的事情,可以想象,一個(gè)對(duì)象指針指向document時(shí),要想復(fù)制它就很困難,因?yàn)閐ocument內(nèi)部有眾多元素并且存在循環(huán)引用的問(wèn)題。
JS中字串不是對(duì)象,它可復(fù)制,但String對(duì)象則不同,它是對(duì)象。串變量、數(shù)值變量等都不是對(duì)象,在賦值時(shí)是復(fù)制,對(duì)象的賦值是指針的復(fù)制,而不是實(shí)際對(duì)象的復(fù)制,JS里指針的復(fù)制可理解為引用。
關(guān)于構(gòu)造器、構(gòu)造器的原型(類(lèi)的原型)、實(shí)例、引用原型:構(gòu)造器是一個(gè)函數(shù),函數(shù)中有個(gè)特殊成員,名為prototype,它是原型對(duì)象。函數(shù)的prototype對(duì)象的成員就是類(lèi)的屬性、方法的定義部分,但portotype的成員不是構(gòu)造器屬性、方法的定義,實(shí)例是用“new 函數(shù)名()”等方法創(chuàng)建的某一個(gè)實(shí)際的對(duì)象。創(chuàng)建對(duì)象時(shí)須由構(gòu)造器創(chuàng)建,實(shí)例繼承類(lèi)的構(gòu)造器portotype中的屬性及方法,為了實(shí)現(xiàn)繼承,實(shí)例以隱藏方式引用原型對(duì)象,這樣當(dāng)調(diào)用繼承的屬性、方法時(shí)不需要指明prototype,除此以外,構(gòu)造器還初始化類(lèi),可動(dòng)態(tài)創(chuàng)建類(lèi)的其它屬性及方法。函數(shù)的原型對(duì)象就好像可供某工程施工者或本工程項(xiàng)目的用戶使用的公用資料、設(shè)備的倉(cāng)庫(kù),構(gòu)造器就象圖紙、施工方案等。類(lèi)的實(shí)例就像是根據(jù)圖紙建起來(lái)的房子(工程案例)。一個(gè)實(shí)例創(chuàng)建后,它就使用了一定的資源,資源的使用量與構(gòu)造器的設(shè)計(jì)有關(guān)。一個(gè)實(shí)例的創(chuàng)建依賴(lài)構(gòu)造器,創(chuàng)建后的實(shí)例稱(chēng)之為某某類(lèi)(某某構(gòu)造器)的實(shí)例。
二、類(lèi)的原型與類(lèi)的實(shí)例的創(chuàng)建
在C++中,使用class來(lái)定義類(lèi)。例:
//-----------
class A{
public:
int p;
A(){ p=3; }
m(){ p++; }
};
A b; //創(chuàng)建實(shí)例b
//-----------
1、在JS中,函數(shù)不僅僅是函數(shù),是一個(gè)對(duì)象實(shí)例。例:
//-----------
a(){…}//創(chuàng)建了一個(gè)實(shí)例a
a.p=3;//為a添加屬性p
c=a;//c變量是對(duì)實(shí)例a的引用
c();//與a();調(diào)用同一函數(shù)
//-----------
2、在JS中既用于定義函數(shù)又可用于創(chuàng)建類(lèi),用于創(chuàng)建類(lèi)時(shí),它取代class定義類(lèi),這時(shí)它就充當(dāng)了類(lèi)的構(gòu)造器的作用,類(lèi)的名稱(chēng)就是函數(shù)名本身。例:
//-----------
a(){//a類(lèi)的構(gòu)造器
this.p=3; //創(chuàng)建public屬性
this.m=(x){this.p+=x;} //創(chuàng)建public方法
}
b=new a();//創(chuàng)建a類(lèi)的實(shí)例
b.m(2);//調(diào)用方法
alert(b.p); //結(jié)果是5
//-----------
b=new a();語(yǔ)句創(chuàng)建a類(lèi)的實(shí)例。用new創(chuàng)建了一個(gè)空對(duì)象,new后的構(gòu)造函數(shù)a()對(duì)其初始化。
上例中this.p=3;是給當(dāng)前對(duì)象動(dòng)態(tài)創(chuàng)建屬性p,p不是a的屬性,卻是b的屬性,a的屬性并不會(huì)復(fù)制給b。構(gòu)造函數(shù)執(zhí)行時(shí)通過(guò)this關(guān)鍵字實(shí)現(xiàn)對(duì)b動(dòng)態(tài)創(chuàng)建屬性p,初值為3,用b.p取得該屬性。
3、this是一個(gè)特殊的對(duì)象,表示當(dāng)前函數(shù)的父對(duì)象。就是說(shuō),誰(shuí)的成員函數(shù)被調(diào)用,該函數(shù)中的this就是誰(shuí)。這樣,通過(guò)this就可將父對(duì)象移到函數(shù)體內(nèi)部來(lái)使用,生存期限為函數(shù)執(zhí)行結(jié)束。JS的全局變量、函數(shù)直接隸屬于window。它們的父對(duì)象是window。調(diào)用一個(gè)函數(shù)時(shí),不指明父對(duì)象,函數(shù)中的this指window。例:
//-----------
var t=3;
alert(this.t);//顯示3
alert(t);//顯示3
//-----------
//-----------
a(){ this.p=3; }
a();//或a(); a()中的this指window,結(jié)果是undefined
b=new Array();
b.c=a;
b.c(); //a中的this指b,結(jié)果是3
//-----------
//-----------
a(){ alert(this.b);}
c=new Array("cc");
c.b=3;
c.m=(){ a(); }
c.m(); //顯示undefined,程序中a();語(yǔ)句沒(méi)用指明父對(duì)象,所以a()中的this指當(dāng)前腳本的祖宗對(duì)象window。
//-----------
一個(gè)比較特殊的情況:new a()創(chuàng)建了對(duì)象,此時(shí)該對(duì)象是a()的父對(duì)象,該對(duì)象只有this可引用得到,a()執(zhí)行后自動(dòng)將this返回。但試圖調(diào)用b.a()是錯(cuò)誤的,因?yàn)闃?gòu)造函數(shù)只能執(zhí)行一次,執(zhí)行后就不在是b的成員了。
例:
4、用new創(chuàng)建對(duì)象的細(xì)節(jié):
使用new a()創(chuàng)建實(shí)例時(shí),首先創(chuàng)建空對(duì)象,并隱藏引用構(gòu)造器的的原型對(duì)象,使得本實(shí)例繼承原型對(duì)象中的所有成員。我們不能直接訪問(wèn)這個(gè)隱藏引用,對(duì)象建后,內(nèi)部引用也建立,這時(shí)如果重建構(gòu)造器中的原型對(duì)象,該構(gòu)造器中的原型對(duì)象引用仍是原來(lái)的,關(guān)于prototype的問(wèn)題下文將詳細(xì)說(shuō)明。其次是執(zhí)行構(gòu)造函數(shù),對(duì)該對(duì)象初始化。其三,將新對(duì)象返回。
三、如何創(chuàng)建私有屬性呢?
當(dāng)函數(shù)內(nèi)部的對(duì)象被注冊(cè)為外部變量時(shí),函數(shù)體內(nèi)的其它變量成為副本保留。注意,父對(duì)象或用new創(chuàng)建的對(duì)象也會(huì)被注冊(cè)到函數(shù)體內(nèi)。
//-----------
a(){
var p=3; //創(chuàng)建private屬性,它是函數(shù)內(nèi)部的變量
cc(){}//創(chuàng)建private方法
this.m=(x){p+=x; return p;} //創(chuàng)建public方法
}
b=new a();//創(chuàng)建實(shí)例
alert(b.m(2)); //結(jié)果是5
//-----------
上例中p為對(duì)象b的私有屬性,對(duì)象的私有屬性、方法只能被其成員方法調(diào)用。每次用new創(chuàng)建對(duì)象時(shí),構(gòu)造函數(shù)內(nèi)部的變量及函數(shù)都會(huì)產(chǎn)生副本,供new創(chuàng)建的對(duì)象的成員函數(shù)使用。如果創(chuàng)建多個(gè)對(duì)象,就產(chǎn)生多個(gè)副本。每個(gè)副本當(dāng)然會(huì)占用一定的內(nèi)存空間,如何減少副本所占的空間呢?有兩種方法可解決,其一是對(duì)函數(shù)做引用處理,其二使用繼承的辦法。這里先講一下前者,后者涉及繼承問(wèn)題,比較麻煩,下文再敘。
當(dāng)把成員函數(shù)移到構(gòu)造函數(shù)外,構(gòu)造函數(shù)在創(chuàng)建方法時(shí)使用函數(shù)作引用即可減少內(nèi)存占用。
mm(x){ this.p+=x;}
a(){
this.p=3; //創(chuàng)建private屬性
this.m=mm; //創(chuàng)建public方法,由于mm是個(gè)函數(shù)對(duì)象,這個(gè)賦值只是個(gè)引用。
}
b=new a();//創(chuàng)建實(shí)例
b.m(2);
alert(b.p); //結(jié)果是5
一般情況下,內(nèi)存占用了就不會(huì)主動(dòng)釋放。
這種方式建立的成員函數(shù)無(wú)法訪問(wèn)private成員。
四、構(gòu)造函數(shù)中能不能有返回值?
在C++中,構(gòu)造函數(shù)是不能有返回值的。而在java中,用new a()已經(jīng)創(chuàng)建實(shí)例,那么返回值又有何用?其實(shí),當(dāng)返回值為對(duì)象時(shí),new創(chuàng)建的實(shí)例不被采用,而使用返回的對(duì)象。如:你返回document對(duì)象、數(shù)組對(duì)象、String對(duì)象(不是串) 、用new創(chuàng)建的對(duì)象等。
a(){
var th=new Array();
th.p=3;
return th;
}
b=new a(); //等價(jià)于b=a();使用new時(shí)多創(chuàng)建了一個(gè)繼承a.prototype的空對(duì)象。private空間不變。
使用上例原理創(chuàng)建對(duì)象有不少好處:創(chuàng)建public屬性、方法是在th中完成的而不是在this中完成的。this用起來(lái)雖然方便但容易造成混亂,當(dāng)程序比較長(zhǎng)是,本人不大喜歡this。private屬性、方法的創(chuàng)建則與前面講的一樣。本例中由于a()有返回值,b接收到的是th對(duì)象,b就是th對(duì)象的引用。與new a()生成的對(duì)象無(wú)關(guān),a對(duì)象中的prototype也不會(huì)被繼承的。有意思的是,當(dāng)a()返回值是對(duì)象時(shí),a()的私有空間沒(méi)有釋放,它做為b的private空間,因此這里的b=new a();與b=a();是一樣的,都能訪問(wèn)其私有空間。再推廣,只要函數(shù)內(nèi)的對(duì)象被返回到函數(shù)體外部或直接賦值(引用)給外部變量,那么該函數(shù)每次執(zhí)行的private空間就不會(huì)被釋放,供這個(gè)外部對(duì)象變量使用,從語(yǔ)句的形式上看,當(dāng)函數(shù)執(zhí)行時(shí),只要讓外部變量引用內(nèi)部對(duì)象,該函數(shù)就已充當(dāng)構(gòu)造器的作用了。例:
//------------------
var kk;
a(){
var c=3;
var th=new Array();
th.m=(){alert(c);}
kk=th;
}
a();
kk.m(); //顯示3
//------------------
當(dāng)調(diào)用函數(shù)創(chuàng)建實(shí)例,與此同時(shí)函數(shù)內(nèi)部對(duì)象也被其它外部變量引用時(shí),那么該實(shí)例與這個(gè)外部變量共用同一個(gè)私有空間。例:
var kk;
a(){
var c=3;
th=new Array();
kk=th;
th.m=(){c++; return c;}
this.m=(){c++; return c;}
}
b=new a();
alert(b.m()); //顯示4
alert(kk.m()); //顯示5
五、使用prototype實(shí)現(xiàn)繼承(靜態(tài)創(chuàng)建成員)
//------------------
a(){ a.prototype.p=3; }
b=new a();//創(chuàng)建實(shí)例
//------------------
portotype是對(duì)象特有的屬性,類(lèi)的原型放在的prototype中,我們稱(chēng)prototype為原型對(duì)象,實(shí)例繼承原型對(duì)象中的所有成員,實(shí)例能過(guò)隱藏引用了構(gòu)造器中的原型對(duì)象實(shí)際繼承,也就是說(shuō)原型對(duì)象中所有成員都可以被實(shí)例直接使用,上例中b.p值為3(因?yàn)閷?shí)例的原型引用是隱藏的,無(wú)須寫(xiě)出prototype),而b.prototype.p則不存在。
用例子說(shuō)明:prototype對(duì)象與c++中的public有一定的相似之處,在prototype中定義公有屬性、事件或方法。a.prototype.p與a.p不是同一個(gè)變量,prototype中的屬性及方法是類(lèi)的原型,在new創(chuàng)建時(shí),a.prototype.p并沒(méi)有復(fù)制給b.prototype.p,因?yàn)閜rototype是對(duì)象特有的,不是普通new生成的對(duì)象固有的,但new創(chuàng)建的對(duì)象內(nèi)部隱藏引用了構(gòu)造器中的原型對(duì)象,這樣新建的對(duì)象就可以通過(guò)這個(gè)內(nèi)部引用訪問(wèn)原型對(duì)象中的成員。b.p也不是a.prototype.p的副本,JS在讀取屬性時(shí),先在自身對(duì)象中找屬性,如果找不到則在它隱藏引用的原型對(duì)象中找。因此,當(dāng)b.p未定義時(shí),b.p就是a.prototype.p的引用而不是副本;當(dāng)b.p定義后,b.p就不再是a.protype.p的引用。顯然執(zhí)行b.p=4是創(chuàng)建了b.p,并不會(huì)改變?cè)蚢.prototype.p的值。因此,原型中方法、屬性具能“透明”特點(diǎn),由該原型創(chuàng)建的實(shí)例都可“透明”的讀取或調(diào)用當(dāng)時(shí)new中的原型對(duì)象的成員而不能直接更改它,除非你引用構(gòu)造器中的原型來(lái)修改。這里強(qiáng)調(diào)一點(diǎn):構(gòu)造器中有原型對(duì)象的引用,實(shí)例中也有原型對(duì)象的隱藏引用,這兩個(gè)引用當(dāng)然指向同一個(gè)原型對(duì)象,如果你在創(chuàng)建實(shí)例后修改了構(gòu)造器中的原型對(duì)象引用,那么實(shí)例中的引用的原型對(duì)象與構(gòu)造器中引用的原型對(duì)象將不是同一對(duì)象。prototype的這些的特性與繼承沒(méi)有太大的區(qū)別。
構(gòu)造器中的portotype里有constructor成員,它引用構(gòu)造器本身。
實(shí)例有個(gè)constructor屬性,它也是繼承來(lái)的,它是對(duì)構(gòu)造器的引用。例:
a(){
this.p=3;
}
a.p=4;
kk=new a();
alert(kk.constructor.p); //結(jié)果是4
六、提高prototype應(yīng)用的效率
a(){
a.prototype.p=3;
a.prototype.m=(){ a.prototype.p=4;}
}
本例中定義了方法m()。由于a()也是構(gòu)造函數(shù),所以在每次用new創(chuàng)建時(shí)實(shí)例時(shí)都會(huì)被執(zhí)行一次,在執(zhí)行過(guò)程中又創(chuàng)建函數(shù)對(duì)象 (){ a.prototype.p=4;},并賦值給m,雖然m只是引用該函數(shù)對(duì)象,但是這個(gè)函數(shù)對(duì)象是新建的,也就是說(shuō)每執(zhí)行一次a()就為m方法創(chuàng)建了一個(gè)新的函數(shù)對(duì)象,這樣是比較耗資源的。如果把m方法的函數(shù)對(duì)象放在a()之外,m對(duì)它做引用就可節(jié)省內(nèi)存。例:
abc(){ a.prototype.p=4;}
a(){
a.prototype.p=3;
a.prototype.m=abc;
}
或:
a(){
a.prototype.p=3;
}
a.prototype.m=(){a.prototype.p=4;}//這樣更好
有得也有失:內(nèi)存節(jié)約了,但沒(méi)能以?xún)?nèi)聯(lián)方式書(shū)寫(xiě)程序,程序看上去稍微亂了一點(diǎn)。
以下舉個(gè)錯(cuò)誤的例子:
a(){
a.prototype.p=3;
}
a..m=(){a.prototype.p=4;}
b=new a();
b.m();//錯(cuò)誤的調(diào)用
a..m=(){a.prototype.p=4;}語(yǔ)句給函數(shù)對(duì)象a添加了方法m(),這個(gè)方法不會(huì)被繼承,僅對(duì)象a自身可使用,只有在prototype中的屬性及方法才會(huì)被繼承。使用new和關(guān)鍵字均創(chuàng)建對(duì)象。一個(gè)創(chuàng)建函數(shù)對(duì)象,一個(gè)創(chuàng)建實(shí)例。
七、創(chuàng)建子類(lèi),即通過(guò)某基類(lèi)創(chuàng)建一個(gè)新類(lèi):
//------------------
a(){ this.p2=2; }
a.prototype.p=1;
a2(){ this.p3=3;}
a2.prototype=new a(); //a2的原型對(duì)象由a生成,當(dāng)然constructor也被繼承
a2.prototype.constructor=a2;//修改constructor,讓它指向自身才時(shí)正確的
a2.prototype.p4=4;
b=new a2();
//------------------
a2.prototype含有a的所有屬性
b通過(guò)內(nèi)部原型引用,查找a2.prototype中的成員
a2.prototype也是用new得來(lái)的對(duì)象,當(dāng)某成員找不到時(shí),也同樣通過(guò)a2.prototype內(nèi)部的原型引用查找a.prototype中的成員。這樣b繼承了a類(lèi)與a2類(lèi)所有的成員。如果a、a2中重名成員,則a2優(yōu)每
構(gòu)造器的標(biāo)準(zhǔn)引用就應(yīng)是引用其自身,a2.prototype.constructor=a2;的作用是使構(gòu)造器引用標(biāo)準(zhǔn)化,因?yàn)閍2.prototype=new a();造成a2.prototype.constructor引用a。
六、大括號(hào)定義對(duì)象,數(shù)組定義類(lèi)
略:
var _object_types = {
'' : ,
'boolean' : Boolean, 'regexp' : RegExp,// 'math' : Math,// 'debug' : Debug,// 'image' : Image;// 'undef' : undefined,// 'dom' : undefined,// 'activex' : undefined,
'vbarray' : VBArray, 'array' : Array, 'string' : String, 'date' : Date, 'error' : Error, 'enumerator': Enumerator,
'number' : Number, 'object' : Object}