一直想寫一篇關(guān)于Javascript面向?qū)ο蟮奈恼?最近終于動(dòng)工了,本來以為不會(huì)寫的很長,可是后來發(fā)現(xiàn)有很多東西要寫,大家先看著這前面的一部分吧,后面有更多的高級(jí)特性陸續(xù)跟進(jìn)中,放心,絕對(duì)不是太監(jiān)貼啊,對(duì)Javascript對(duì)象不太了解或者沒有了解的人可以仔細(xì)看看哦,有錯(cuò)誤之處大家多多指正哦,本人水平有限
(1)為什么要面向?qū)ο?/span>
在十年前或者也許更晚的時(shí)候,javascript都是一種被人當(dāng)作玩具來使用的語言,大多時(shí)候,沒有人樂于深入研究它的特性,而只是用它來實(shí)現(xiàn)各種花里胡哨的特效來炫耀自己的技術(shù)。一時(shí)之間,各種網(wǎng)站中都充斥了噪音般的所謂的特效,直至今日,仍然有許多人沒有清醒過來。但是Ajax的出現(xiàn)可能促使了大量的設(shè)計(jì)師開始關(guān)注Javascript,從而扭轉(zhuǎn)了互聯(lián)網(wǎng)的表現(xiàn)。的確,互聯(lián)網(wǎng)是一個(gè)方便用戶的地方,而不是一個(gè)炫耀個(gè)人技巧的地方,簡單易用才是我們應(yīng)該堅(jiān)持的原則。
互聯(lián)網(wǎng)趨向于簡單易用,那Javascript還有什么意義呢?很多人說畢竟它只是一個(gè)玩具啊!和flash,silverlight比起來它的表現(xiàn)形式實(shí)在是太單調(diào)了,可是Javascript遠(yuǎn)沒有想象的那么幼稚,它是一門發(fā)展了將近二十年的各方面都很完善的語言,它的表現(xiàn)豐富,雖然在圖形方面它有著一定的缺點(diǎn),但是在與html的交互中它卻有著極其強(qiáng)大的功能,它雖然在很多特性方面都很簡單,但是它的確是一門非常靈活的語言。
對(duì)于Javascript,很多初學(xué)者喜歡將其作為一種類似c的函數(shù)式語言來使用,這種寫法的特點(diǎn)就是除了全局變量就是函數(shù),這樣寫代碼也未嘗不可,但是它有著先天的弊端,首先它會(huì)創(chuàng)建大量的全局變量,全局變量過多會(huì)造成內(nèi)存泄漏(如果沒有在使用完之后手動(dòng)回收的話),而且全局變量在腳本中的任何位置都是可見的,很多時(shí)候會(huì)不小心在某個(gè)函數(shù)里定義了一個(gè)和全局變量名字相同的變量,這樣會(huì)造成混淆,這種混淆是致命的。第二個(gè)缺點(diǎn)也是為什么現(xiàn)在所有的語言都以面向?qū)ο笞鳛樽约旱馁u點(diǎn)的原因,因?yàn)楫?dāng)程序邏輯變得越來越復(fù)雜的時(shí)候,代碼關(guān)系越來越復(fù)雜,想象有1000行代碼,其中有50個(gè)函數(shù),這50個(gè)函數(shù)之間的關(guān)系又相互依賴,當(dāng)你寫到第1001行的時(shí)候突然想到要向某個(gè)關(guān)系中再插入一個(gè)函數(shù),你記得這個(gè)關(guān)系鏈在哪里嗎?其中的關(guān)系又如何呢?如果你能用這種方式來寫代碼,而又可以記住無數(shù)復(fù)雜的關(guān)系,即使睡一覺也不會(huì)忘記的話,你就是天才??上觳攀菦]有這樣寫代碼的,高手寫代碼會(huì)考慮很多東西,例如:松耦合,重用性,內(nèi)存回收,閉包特性,封裝,下面會(huì)涉及到一些相關(guān)的東西。
(2)對(duì)象與函數(shù)
Javascript的對(duì)象是什么呢?讓我們先來想想在其他聲稱面向?qū)ο蟮恼Z言中它是什么吧,其實(shí)面向?qū)ο笤谖依斫鈦砭褪且环N組織代碼的方式,它可以封裝一些屬性和方法到一個(gè)類,這個(gè)類大多時(shí)候是現(xiàn)實(shí)世界的抽象表達(dá),然后它可以繼承,可以多態(tài),可以實(shí)例化。
在Javascript中,我認(rèn)為它的面向?qū)ο蠛推渌Z言的面向?qū)ο笥兄举|(zhì)的區(qū)別,因?yàn)樵贘avascript中沒有類的概念,一切都是對(duì)象,對(duì),一切都是對(duì)象,但是一切又可以寫成完全沒有對(duì)象的影子的形式,在Javascript中的對(duì)象和面向?qū)ο蟮膶?duì)象有著概念上的不同,Javascript中的對(duì)象就是一個(gè)基本的實(shí)體,例如:html元素中的一個(gè)按鈕,它就是一個(gè)按鈕對(duì)象,沒有實(shí)例化自任何類,但是你可以靈活地操作它,可以憑空生成它,也可以動(dòng)態(tài)刪除它,看起來的話,我們感覺這更符合我們理解世界的方式,我們不需要任何抽象,一個(gè)我們能看到的東西就是一個(gè)對(duì)象,我們無需想象它原來的抽象的樣子,只需要知道它就是一個(gè)實(shí)實(shí)在在的對(duì)象就行。而在其他語言中,你要?jiǎng)?chuàng)建一個(gè)對(duì)象就必須先創(chuàng)建一個(gè)抽象的表示(類),然后實(shí)例化,這更像是一種組織代碼的方式,而不是一種操作現(xiàn)實(shí)世界的體驗(yàn),所以相比于其他的語言,我感覺Javascript的對(duì)象更充滿了讓人興奮的元素。
與很多編程語言類似,Javascript中的頂級(jí)對(duì)象是Object(),它是所有對(duì)象的父類對(duì)象,所以在Javascript中可以用 var myobject=new Object()來定義一個(gè)對(duì)象,但是實(shí)際上這種寫法沒有什么意義,因?yàn)镴avascript中的變量是弱類型的,在初始化的時(shí)候即使你定義var myobject=0,在稍后你仍然可以將一個(gè)對(duì)象賦給myobject變量,這種特性是所有的腳本語言都基本具有的特性,它使你不必太關(guān)心操縱的變量的類型.
Javascript是一門很奇特而靈活的語言,從Function()頂層對(duì)象就可以看出來,F(xiàn)unction對(duì)象是頂層對(duì)象,這意味著什么呢?上例子:
在Javascript中更多時(shí)候我們是這樣來創(chuàng)建一個(gè)基本的對(duì)象的:
1 var myobject=function(param1,param2){
2 this.name=param1;
3 this.age=param2;
4 this.showmsg=function(){
5 alert("name:"+this.name+"<br />"+"age:"+this.age);
6 }
7 }
這個(gè)基本的對(duì)象擁有兩個(gè)屬性和一個(gè)方法。如果你以前沒有接觸過面向?qū)ο蟮腏avascript的話,你一定會(huì)說我定義了一個(gè)函數(shù),但是你也許會(huì)對(duì)于函數(shù)內(nèi)的this指針感到迷惑,事實(shí)上,我們的確定義了一個(gè)函數(shù),但是在Javascript中函數(shù)是頂級(jí)對(duì)象(而不是其他語言中的只充當(dāng)封裝一小部分功能的那種函數(shù)),上面的代碼相當(dāng)于定義了一個(gè)對(duì)象,而this則指向了你定義的myobject對(duì)象。
關(guān)于this指針?biāo)傅膶?duì)象事實(shí)上不在本教程的討論范圍之內(nèi),但是在一個(gè)對(duì)象內(nèi)部它指向的是自己所屬的對(duì)象,而在某個(gè)事件處理函數(shù)里,它指向的是接受事件的某個(gè)DOM元素,具體可以去google一下。
至于Javascript內(nèi)部是如何實(shí)現(xiàn)對(duì)象機(jī)制的,或許不必關(guān)注太多,而只需要了解有哪些定義對(duì)象的方法和如何操作對(duì)象即可,事實(shí)上,Javascript內(nèi)部的對(duì)象就是一個(gè)關(guān)聯(lián)數(shù)組,由以名稱作為鍵的字段和方法組成,這從如何遍歷一個(gè)Javascript對(duì)象就可以看出來,就拿上面定義的myobject來說吧,遍歷方法如下:
var mynew=new myobject('a','b');//首先初始化一個(gè)對(duì)象,像這種帶有參數(shù)的對(duì)象需要先初始化
for(obj in myobject){
alert(myobject[obj]);//彈出對(duì)象包含的所有元素
}
結(jié)果將彈出三次窗口,分別是 a,b,function(){……}
定義對(duì)象(函數(shù))的方法有很多種,下面列舉其中的幾種:
1.function myobject(){}
2.var myobject=function(){}
3.var myobject=new Object();
myobject.name="";
myobject.age="";
4.JSON方式,這是一種比較特殊但是又常用的方式,并且JSON可以用于與服務(wù)器交互信息,它的格式顯而易見,結(jié)構(gòu)清晰,具有比xml先天的優(yōu)勢(shì),并且它是純字符串,可以方便地在服務(wù)器和客戶端之間傳送,具體可以去google搜索一下,因?yàn)樯婕暗搅艘粋€(gè)較大的領(lǐng)域,而我們本文的目的是介紹一些Javascript的高級(jí)特性,故暫不提及JSON
(3)從一個(gè)對(duì)象創(chuàng)建實(shí)例?
從對(duì)象創(chuàng)建一個(gè)實(shí)例說起來貌似是很簡單的東西,是啊,基本在所有的語言中,都是用new關(guān)鍵字來創(chuàng)建實(shí)例的,Javascript當(dāng)然也不例外,可是關(guān)于對(duì)象的引用問題,你考慮過么?在php4中曾經(jīng)采取了拷貝對(duì)象的方法來產(chǎn)生更多的對(duì)象,也就是說每個(gè)對(duì)象都是一個(gè)獨(dú)立體,而不是通常的一系列對(duì)象共享類的方法,也就是為每個(gè)對(duì)象都會(huì)復(fù)制一份函數(shù),這種工作方式造成在php4時(shí)代很多初學(xué)者對(duì)這個(gè)概念混淆不清,造成許多編碼漏洞,而在Javascript同樣存在同樣的問題,而在客戶端的這種對(duì)象的復(fù)制方式,極其容易造成內(nèi)存泄漏,因?yàn)槊看萎a(chǎn)生新的實(shí)例都會(huì)復(fù)制所有的屬性和方法,占用大量的內(nèi)存.
然而在Javascript中事實(shí)上是有解決方案的,在普通方式下,我們是這樣產(chǎn)生一個(gè)實(shí)例的:
var myobject=function(param1,param2){
this.name=param1;
this.age=param2;
this.showmsg=function(){
alert("name:"+this.name+"<br />"+"age:"+this.age);
}
}
var objectone=new myobject('a','b');
var objecttwo=new myobject('a','b');
var objectthree=new myobject('a','b');
看起來不錯(cuò),用起來也不錯(cuò),對(duì)象之間互相沒有任何干擾,也能正常完成操作,一切看起來理所當(dāng)然,但是你每次產(chǎn)生一個(gè)新對(duì)象的時(shí)候,腳本引擎都會(huì)給對(duì)象復(fù)制一份屬性和方法,有沒有覺得這樣很浪費(fèi)內(nèi)存呢?在大型Javascript應(yīng)用中首先考慮的就是內(nèi)存問題,本文后面也會(huì)涉及這方面問題,所以這種方法應(yīng)該杜絕使用.
正確的使用方法是 用prototype關(guān)鍵字來定義一個(gè)類的方法或者屬性
例如:
1
2 var myobject=function(param1,param2){
3 this.name=param1;
4 this.age=param2;
5 }
6 myobject.prototype.showmsg=function(){
7 alert("name:"+this.name+"<br />"+"age:"+this.age);
8 }
9 var objectone=new myobject('a','b');
10 var objecttwo=new myobject('a','b');
11 var objectthree=new myobject('a','b');
這樣的話你創(chuàng)建的對(duì)象之間可以共用方法,也就是msg()函數(shù)只定義了一次,其他的對(duì)象公用這一個(gè)方法,而不是復(fù)制出自己的方法.
(4)擴(kuò)展內(nèi)建的類
和其他面向?qū)ο蟮恼Z言一樣,Javascript也有自己的內(nèi)建類,包括Array,Date,String之類的,這種特性允許你可以編寫自己常用的方法來附加在這些內(nèi)建類上,然后將這些方法包裝到一個(gè)js文件中,以后調(diào)用即可免去多次編寫的麻煩.Javascript內(nèi)建的方法是有限的,很多其他編程語言的特性它都沒有包含,這時(shí)候就需要我們自己去編寫這些方法,下面具體說明:
例如,很多時(shí)候我們需要Javascript中的數(shù)組擁有打亂順序的功能,這在游戲或者其他應(yīng)用中經(jīng)常用到,那你就可以用這種方法來實(shí)現(xiàn):
//生成隨機(jī)數(shù)字函數(shù)
function rand(x){
return Math.ceil(Math.random()*x);
}
//生成隨機(jī)數(shù)組的一個(gè)擴(kuò)展方法
Array.prototype.random=function(){
for(var i=(this.length-1);i>1;i--){
var j=rand(this.length-1);
var cache=this[i];
this[i]=this[j];
this[j]=cache;
}
}
在上面的例子中,我們給數(shù)組元素添加了一個(gè)方法random(),它的作用就是隨機(jī)打亂數(shù)組,在你的腳本中添加了這段語句之后,你就可以給任何的數(shù)組對(duì)象使用這個(gè)語句了,使用方法很簡單,例如某個(gè)數(shù)組變量名叫做:myarr.你執(zhí)行myarr.random()后,myarr數(shù)組的元素就被打亂順序了,這樣的擴(kuò)展是不是很有用呢.
(5)對(duì)象的反射
反射是一種對(duì)象的機(jī)制,它允許你在完全不了解對(duì)象的情況下了解它的屬性和方法,通常情況下,程序員對(duì)于自己所操縱的對(duì)象是如何組成的是非常了解的,但是在某些特殊情況下使用某個(gè)其他人寫的復(fù)雜的對(duì)象的時(shí)候,我們需要快速了解這個(gè)對(duì)象的屬性和方法,就需要用到反射的機(jī)制,當(dāng)然反射的應(yīng)用并不是局限于此,這里并不介紹它的應(yīng)用領(lǐng)域,而只是介紹反射在Javascript中的使用方法.
首先我們可能想知道在某個(gè)對(duì)象中是否存在一個(gè)特定的屬性或者方法,這時(shí)候我們可以簡單地測(cè)試它:
if(myobject.someproperty){
……
}
這樣可以簡單測(cè)試,但是存在弊端,因?yàn)槿绻@個(gè)屬性的返回值正好是 false,0,null,那么這樣的測(cè)試將出現(xiàn)問題,更嚴(yán)謹(jǐn)?shù)姆绞绞沁@樣的:
if(typeof(myobject.someproperty)!="undefined"){
……
}
在Javascript中如果沒有定義一個(gè)對(duì)象或者變量的話,它總是返回 undefined類型.
也可以用其他的內(nèi)建類來縮小測(cè)試范圍:
if(myobject instanceof Object){
}
instanceof 是用來測(cè)試內(nèi)建類或者自定義類的操作符,內(nèi)建類指Array,Funtion,Date之類的內(nèi)建類.這里有兩個(gè)問題,首先用來測(cè)試JSON方式創(chuàng)建的對(duì)象的時(shí)候,因?yàn)樗偸欠祷匾粋€(gè)Object或者Array,所以不能確定它具體歸屬的自定義類.第二個(gè)問題是有關(guān)各個(gè)內(nèi)建類的繼承關(guān)系,例如:Function和Array都是繼承自O(shè)bject類的,所以如果你在代碼中如果測(cè)試某個(gè)Array對(duì)象的話,如果你先測(cè)試它是否是Object,將返回true,如果測(cè)試它是否是Array的話,它也會(huì)返回true,這取決于你的測(cè)試順序.
一個(gè)更簡單而有用的方法,是遍歷一個(gè)對(duì)象的所有屬性和方法來快速了解一個(gè)對(duì)象的內(nèi)部狀態(tài):
function myobject(){
this.name="name";
this.age="age";
this.sex="sex";
this.func=function(){
}
}
var myobj=new myobject();
for(var i in myobj){
alert(myobj[i]);
}
這樣將順序彈出對(duì)象的所有屬性和方法
(6)Javascript接口的實(shí)現(xiàn)
接口是一種面向?qū)ο蟮钠毡闄C(jī)制,它指定一些行為但是不提供具體的實(shí)現(xiàn),這在實(shí)際應(yīng)用,特別是大型項(xiàng)目中是很有用的一種機(jī)制,它提供了一個(gè)實(shí)現(xiàn)來在不同的對(duì)象之間進(jìn)行協(xié)調(diào)通信,它通常提供一種類似契約的機(jī)制,通過這個(gè)契約,程序員不必去考慮某個(gè)接口的內(nèi)部實(shí)現(xiàn),也不必考慮它在其他庫代碼中的實(shí)現(xiàn).正如"接口"這個(gè)詞的普遍概念,聽到這個(gè)詞,或許很多人會(huì)想到在硬件系統(tǒng)中某個(gè)接口的概念,在硬件系統(tǒng)中,接口也是提供一種向外的實(shí)現(xiàn),外部設(shè)備可以通過接口與這個(gè)設(shè)備通信而不必知道其內(nèi)部實(shí)現(xiàn),同時(shí)它又可以與很多外部接口通信,因?yàn)橹灰谕獠吭O(shè)備實(shí)現(xiàn)了與接口的統(tǒng)一就可以與該接口協(xié)調(diào)應(yīng)用.
接口在Java或者c#中可以實(shí)現(xiàn)多重繼承,它將多個(gè)類所需繼承的一些公共方法抽象出來,接口封裝的就是這些公共方法的行為規(guī)范,接口規(guī)定在其子類中必須實(shí)現(xiàn)它所有的方法,所以接口看起來更像一種強(qiáng)制的規(guī)定,這種規(guī)定使我們能夠嚴(yán)格遵守約定,從而實(shí)現(xiàn)多個(gè)繼承類之間的統(tǒng)一.
在Java中,假設(shè)有一個(gè)形狀接口,它規(guī)定了作為一個(gè)形狀所必須擁有的方法,而繼承自它的類(例如:圓形類,方形類)必須實(shí)現(xiàn)所有的方法,之后假設(shè)在某個(gè)普通類中我們定義一個(gè)方法來計(jì)算面積:
public double addAreas(Shape s1,Shape s2)
{
return s1.getArea()+s2.getArea();
}
我們用形狀接口定義了參數(shù),這使我們可以自由更改參數(shù)的具體類型,例如如果我們實(shí)現(xiàn)了一個(gè)circle類,它繼承自shape接口,那么我們可以如此調(diào)用這個(gè)方法:......addArea([circle] s1,[circle] s2);
這并不是一個(gè)完整而正確的程序,而只是要表達(dá)一個(gè)意思,就是參數(shù)可以用任何實(shí)現(xiàn)了接口的類的實(shí)例來代替,這就是接口的有用之處,你無需知道形狀接口下面繼承了什么類,但是可以肯定的是它們都實(shí)現(xiàn)了接口的方法,我們?cè)诔绦蛑兄恍枰褂媒涌诙x參數(shù)類型即可,以后可以用接口的任何子類來代替參數(shù).
在強(qiáng)類型的編程語言中,接口還算一個(gè)比較清晰而且較容易實(shí)現(xiàn)的功能,而在弱類型的Javascript中實(shí)現(xiàn)接口有著前所未有的困難,因?yàn)樵贘avascript中無法預(yù)定義變量的類型,像上面的方法只能定義成這樣:
function addAreas(s1,s2)
{
return s1.getArea()+s2.getArea();
}
我們可以向參數(shù)傳入任何類型的對(duì)象,而不是接口所提供的嚴(yán)格的約定那樣.其實(shí)在Javascript中實(shí)現(xiàn)真正的接口是不可能的,更何況在Javascript中實(shí)現(xiàn)繼承也是一件不容易的事情,在Javascript中的接口實(shí)際上就是在方法內(nèi)測(cè)試傳過來的參數(shù),在此我們給其附加一些約定,這樣,我們就實(shí)現(xiàn)了一種約定,如果傳過來的參數(shù)不符合這個(gè)約定,方法就不能正常執(zhí)行,這只是一種類似接口的約定方式.
我們可以定義很多約定,常用的包括檢查參數(shù)對(duì)象中是否包含某個(gè)方法.一種復(fù)雜一點(diǎn)但是完美無缺的方案是給系統(tǒng)內(nèi)建的Object頂級(jí)對(duì)象附加一個(gè)方法來判斷其內(nèi)是否包含某個(gè)方法:
Object.prototype.implement=function(funcname){
return this&&this[funcname]&&this[funcname] instanceof Funtion;
}
這里應(yīng)用了前面介紹的反射機(jī)制來進(jìn)行判斷對(duì)象自身的內(nèi)容.
這樣在方法addAreas(s1,s2)中我們就可以約定s1,s2必須包含getArea()的方法了
寫法如下:if(s1.implement("getArea")) ......
還可以限制必須是數(shù)字類型:
function isNum(arg){
return parseFloat(arg)!=NaN;
}
parseFloat()將返回參數(shù)中的數(shù)字部分,NaN是一個(gè)非數(shù)字的類型,但是因?yàn)橹禐?64any"的字符串也會(huì)返回時(shí)數(shù)字,所以我們將用parseFloat()先去掉所有的非數(shù)字部分,然后判斷,是否是數(shù)字類型.
其他的限制可以自己探索一下,基本原理基本相同,當(dāng)我們給方法加的限制越多的時(shí)候就限制了一個(gè)越嚴(yán)謹(jǐn)?shù)慕涌?
未完,待續(xù)
如對(duì)本文有疑問,請(qǐng)?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會(huì)為你解答??! 點(diǎn)擊進(jìn)入論壇