再論物件導向的Javascript程式設計

weixin_34304013發表於2007-08-14

關鍵字:Javascript OOP 物件導向 Jscript 原型 prototype

提要:

       在原先《物件導向的JavaScript程式設計》中,筆者提出了通過Javascript(確切意義上面來說是MicrosoftJscript)來實現OOP的思想,原文中因為筆者知識的侷限,存在一些沒有理清楚的思路,感謝一些網友提供的評論,結合這段時間的實際工作,筆者修正了原文中的一些想法.

 

       因為個人知識水平有限的原因,以下所提到的JavaScript僅僅止於Microsoft Jscript,至於和NetScape之間的一些不同,也僅止於筆者所知道的範圍。在原文中筆者提出了物件導向的JavaScript的概念,不是希望在賣弄Script的技巧,只是覺得在開發的過程中或多或少的會用到,因此從個人的角度提出一些相對可行性的建議。

 

       原文對於Javascript提出了實現,封裝,繼承,過載,事件等等各個概念,因為個人理解的偏差,原文中有許多地方不甚貼切之處,因此本文只是對於原文的一些個人看法的一些修正。

 

       JavascriptOOP的角度來說,應該不是一門純OOP的語言,更加準確來說,是一門Object-Based的指令碼語言,因此原文提到的所謂類的概念,只是從傳統OOP語言的角度去描述的,而所謂的類實現,應該是一種原型實現方式,因為在整個實現的過程中prototype是一個最重要的體現。

 

       理論上來說,所有的function都是object,因此類的建立可以完全基於Object來實現的,原文采用function只是為了更好的實現一些功能,以下就實現,封裝,繼承,過載,事件這5個概念作一些具體的闡述。

      

1.   實現。

Javascriptfunction是一等公民(hax原語),因此採用function來實現是最好的方式,至於採用如下兩段程式碼來實現這個就是根據個人程式設計的習慣來決定,而一些實現的偏差我會在下文詳細的描述。

 

程式碼1

function MyObject(sName){

    this.name=sName;

    this.MessageBox=function(){

           alert("名字"+ this.name);

    }

    this.ChangeName=ChangeName;

    function ChangeName(){

           this.name="改變" + this.name;

    }

}

 

 程式碼2

 

function MyObject(sName){

       this.name=sName;

}

MyObject.prototype.MessageBox=function(){

       alert("名字"+ this.name);

}

MyObject.prototype.ChangeName=function(){

       this.name="改變" + this.name;

}

 

採用標準的類實現結構和原型實現方式,在使用來說是沒有區別的,如果從傳統的類設計如(C++smalltalk那樣的語言)來說,應該採用第一種方案,但是就我個人而言,我建議使用第二種方案,畢竟在開發的過程中,那樣的編寫方式和Javascript的編寫方式比較接近,基本立足於function的實現。

 

2.封裝

      既然引入了物件的概念,必然需要考慮到封裝,C++這樣的語言中有提出private ,protected,public的概念,在Javascript中,沒有對應protected的概念,但是在privatepublic能夠做到隱含的實現,另外如果需要大量使用到私有變數,實現方式中採用方案一可能更加合適一點,如下程式碼所示。

 

function MyObject(sName){

       var mVar=1;

       this.name=sName;

       this.MessageBox=function(){

              alert("名字"+ this.name);

              mVar=2;

       }

       this.ChangeName=ChangeName;

       function ChangeName(){

              this.name="改變" + this.name;

       }

       this.GetVar=function(){

              return mVar;

       }

}

 

mVar是一個私有變數,name是一個共有變數,在MyObject內部實現的函式,可以訪問mVar變數,而name則可以通過this.name的方式在任何實現的地方訪問,對於內部函式的處理,ChangeName是不可以被直接訪問的,但是可以通過Object.ChangeName()訪問,這些概念在hax的評論中有幾個網友講述的比較詳細.

 

簡單而言,Javascript提供了物件中的privatepublic,但是並不提供顯式宣告,在類函式(姑且讓我如此稱呼)中定義的函式和變數都是私有變數,通過this.methodName或者this.varName定義的都是public實現的,通過object.prototype.name實現的也是public,在類函式中的私有函式可以訪問那些私有變數和函式的,至於通過object.prototype.name那樣實現的方法是否可以訪問私有變數,我沒有做過,如果誰做過,麻煩告訴我。

 

VBS中提出了class的概念,如下的程式碼能夠完整地體現封裝的風格。

Class MyClass

       Private vLoginID

       Public UserName

       Public Property Get LoginID

        LoginID=vLoginID

       End Propertyp

       Public Property Let LoginID(vValue)

              vLoginID=vValue

       End Property

End Class

 

Javascript中沒有顯式所謂屬性概念,這點上面VBS的封裝確實比較乾淨,JavaScript

中沒有這樣的概念,但是可以採用和Java類似的設計方法,如Get_PropertyName,Set_PropertyName這樣的方法來做。我個人建議就採用函式來實現,如果如果帶引數,則表示進行set操作,如果不帶引數,可以認為是get操作。簡單而言採用如下的程式碼風格。

Object.prototype.UserName=functionvValue){

    If(vValue){

           //todo get

    }

    Else{

           Return value;

    }

}

       以上提及的只是我個人實現的觀點。

3.繼承

       原文中提到通過設定原型的方式可以實現繼承,就是通過SubObject.prototype=new BaseObject這樣的方式來實現,至於對應的建構函式,在原文中採用了

This.base=parentclass;

This.base();

 

這樣的方式實際是呼叫父類的建構函式來完成的,完成prototype(原型)設定之後,通過SubObject例項化的物件就可以訪問父類的所有方法。在實際開發過程中碰到如下的問題,本來考慮到實現的時候通過方案一和方案二是沒有任何區別的,可是如下的程式碼卻有點奇怪。

function son(sName){

       this.base=MyObject;

       this.base(sName);

      

}

 

son.prototype.MessageBox=function(){

       //this.base.MessageBox();

       alert("子類呼叫");

}

son.prototype.hi=function(){

       alert("kk");

}

var o=new son("hello");

o.MessageBox();

 

最後的執行結果是上文MyObject.MessageBox的方法,son.prototype.MessageBox這個函式卻不執行,不清楚是否是我的程式碼還有問題,在繼承方面,我發現通過this.method=functionNameobject.prototype.methodName實現似乎不是完全一樣的,對於這個方面如果有誰比較理解,麻煩告訴我。

 

在剛才的程式碼中不使用prototype就可以完成繼承,這裡採用的方法應該是建構函式初始化,因此Object.MessageBox等等一些函式都完全初始化,而如果沒有寫SubObject.prototype=BaseObject,那麼BaseObject.prototype.methodName來實現的一些方法就無法實現。

 

4.過載

在原文中我提到使用Object.prototype.MethodName的方式來重寫方法,但是同時保留了一個問題,就是如何呼叫父類的方法,上文提到的this.base=MyObject,this.base()這樣只能夠實現建構函式的實現的一些方法,也就是父類在函式體內本身定義的一些方法或者屬性,通過原型prototype實現的就無法實現,在採用SubObject.prototype=new BaseObject那樣的方式,也會出現一些上文提到的一些問題。

Jscript 5.5以上版本,有一個call函式,通過這個函式我們可以完整的實現對於父類的方法呼叫,就如同Java裡頭的super,也就可以實現過載。如果讀者的客戶端系統是IE6或者WinME,就已經是Jscript5.5以上版本,至於XP或者2003我就不用多說了,Jscript引擎版本比較低的我建議下載高版本的。

BaseObject

-------------------

function BaseObject(){

     this.msg=null

}

BaseObject.prototyp.Message(msg){

 this.msg=msg;

 alert(this.msg);

 this.msg="change"+this.msg;

}

 

SubObject

function SubObject(){

   BaseObject.call(this);

}

SubObject.prototype=new BaseObject;

SubObject.prototype.Message=function(msg){

    BaseObject.prototype.Message.call(this,msg);

    alert(this.msg);

}

 

 呼叫程式碼

Var v= new SubObject();

v.Message(“hello”);

 

對於父類的方法通過call去呼叫,call(this,args)表示當前例項呼叫,在SubObject中,BaseObject.call(this)呼叫父類的方法,在過載Message函式的時候,為了維繫原有的功能,首先呼叫父類的Message方法,然後才編寫自己的實現程式碼。

 

從程式碼中大家可以明顯看到是使用BaseObject.prototype.MethodName.call(this,args)這樣的方法呼叫的,因為使用到了call函式,所以需要比較高版本的支援。

 

以上我討論的是針對Jscript的實現,至於在別的引擎如NetScape或者Flash Script中,可以通過__proto__這個來返回父類的原型,因此呼叫上就簡單許多,this._proto__相當於Java裡頭的super,在這裡可以直接呼叫。

 

在低版本的Jscript引擎中我不知道是否可以通過instanceOf或者別的函式來共同實現,這也希望大家來共同探討。

 

5.事件

      本來,在OOP程式設計中,我不應該討論如此的問題,但是在實際的應用中可能需要使用到類似事件的形式,因此在原文中,基本是採用了類似C++中虛擬函式的方法,不過在定義的時候只是使用了一個普通變數的方法。

 

       如果類本身是和dom繫結的,對於HTML可以使用fireEvent函式來引發一些事件,不過這些應該已經屬於DHTML討論的東西了。

 

       Javascript本身上來說沒有所謂真正意義上面的物件導向,原文和本文提及的也只是正對實際開發工作中的一些邏輯實現,在瀏覽器內部確實不是適合大量的使用OOP的東西,但是對於Jscript Library開發人員來說,物件導向確實能夠提供不少的好處,筆者寫本文的目的也是希望能夠對於Jscript Library的開發人員有說幫助,至少能夠作為一個參考的資料。

 

       因為本身水平有限,在一些的理解上面可能有些偏差,也希望有人能夠給我指出來。

相關文章