物件導向的JavaScript編程
作者沒有看過netscape的文檔,也沒有看過ECMAScript(ECMA-262)規範,僅僅看msdn是不能真正懂得javascript的。
下面我來指導一下吧。
> 物件導向的JavaScript編程
JavaScript雖然可以認為是一個對象語言,但是與大家熟悉的c++,java不同。主要的區別在於:
1. js是基於對象的語言,而不是嚴格的物件導向語言。
2. js的對象是基於原型的。什麼是prototype?可以去看看《設計模式》中的prototype模式。
3. js的function是一等公民,js不區分class和object。
> Javascript對於做過Web程式的人不應該是陌生,初期是用來做一些簡單的FORM驗證,基本上是在玩弄一些技巧性的東西。IE 4.0引入了DHTML,同時為了對抗Netscape的Javascript,提出了自己的指令碼語言JScript,除了遵循EMAC的標準之外,同時增加了許多擴充,如下要提到的OOP編程就是其中的一個,為了命且概念,我以下提到的Javascript都是Microsoft Internet Explorer 4.0以上實現的JScript,對於Netscape,我沒有做過太多的程式,所以一些的區別也就看出來。
很多人都僅僅玩弄js技巧,其實是半瓶水的表現。在瀏覽器中過分賣弄js,卻不懂得寫noscript標籤,這是典型的還沒有明白web之道的表現。DHTML這種廠商對抗的東西已經沒有什麼好說的了,大家自己去看DOM標準吧。順帶說一下,netscape有js的guide和reference,建議真正想深入js的人至少要去看一遍,msdn上對js語言的解說雖然一個版本比一個版本多了,但是始終是不如創造語言的公司說得清楚。或者推薦大家看奧萊理的《js權威指南》,我隨手翻過,是有史以來最好的js書之一。
> Javascript不是一個支援物件導向的語言,更加算不上一個開發平台,但是Javascript提供了一個非常強大的基於prototype的物件導向調用功能,你可以在你自己需要的地方使用他們。因此,如何使用對象?本文儘可能從Javascript物件導向實現原理出發,解析清楚它的工作模型。在瞭解這些模型之後,你可以在自己的指令碼庫中編寫一些實現代碼,然後在其他地方調用。
作者這裡說的對的,js本身是指令碼語言,而且是依賴於host的語言(根據ecma規範的說法),因此很多人關於js如何如何的說法都是錯誤的,因為他們說的是dhtml或者dom,總之是瀏覽器環境,而不是js語言的特性。ms的dhtml可以用於js和vbs,DOM是語言獨立的。js本身是單純的語言,雖然主要以瀏覽器環境為設計目標,但並不影響他在其它環境中發揮作用。例如可以作為伺服器端指令碼,比如asp(雖然多數用vbs),或者jsp的一種指令碼語言(注意js不是jsp,jsp通常使用java作為指令碼語言,但是也可以用其他指令碼語言)。“基於prototype的物件導向調用功能”這句話就有問題了。prototype不是調用功能,而是語言的機制。而且“物件導向”的提法也不完全準確。從術語使用上講,物件導向一般指c++,java,smalltalk等,js只被叫做“基於對象”的,當然我覺得這也不是很重要。
> Javascript的文法和C++很接近,不過在類實現中沒有使用關鍵字Class,實現繼承的時候也沒有採用傳統的Public或者Implement等等所謂的關鍵字來標示類的實現。這樣的情況下,可能有就有人會問,如何編寫Javascript的Class,如何?繼承。我開始也是百思不得其解,後來看了MSDN,才知道採用了prototype來實現,包括繼承和重載,也可以通過這個關鍵字來實現。
js的文法是c一路的(java,c#也是)。不僅是沒有關鍵字class那樣,嚴格來說,js沒有類的(當然也沒有繼承一說了)。但是通俗來說,可以認為,js中有類似“類”的東西。順帶說c++不是“完全”的對象語言,因為缺乏“類對象”,呵呵(不過這是語言的不同設計,未必是缺點)。至於imp,那是介面了,許多語言沒有明顯介面而用多重繼承、虛類、抽象方法來類比,比如c++。prototype並不只是一個關鍵字,而是js的特質之一。如何繼承,雖然你可以從msdn摸索,不過是事倍功半,還是再勸你直接看netscape的guide,或者mozilla上也有。
> Javascript的函數很奇怪,每個都是預設實現了Optional的,即參數都可以可選的,function a(var1,var2,var3),在調用的過程中a(),a(value1),a(value1,value2)等等的調用都是正確的,至少在即使編譯部分可以完整通過,至於其它,只是和函數的實現邏輯比較相關了。
這沒有什麼奇怪的,只是js的許多特性(動態類型,沒有同名函數……)的結果。補充一個,在function之內,可以用arguments像數組一樣巨集指令引數。
> 以下就JS對於類的實現、繼承、重載詳細介紹其實現方式。
> 1。實現
> Js類的實現就通過函數直接實現的,每個函數可以直接看成class,如下代碼
應該這樣說,js沒有類,但是js中,一切都是對象,包括函數。因此類可以通過做一個建構函式(注意,建構函式是來構造對象而不是構造類的)來類比。
> 對於類的屬性,可以通過兩種方式實現
> 2)通過ClassFunction.prototype.[FunctionName]=function(var1,var2...){//todo}這樣的方式完成調用。
先糾正用詞,這裡不是調用而是賦值。
> 這兩種方式從目標來看是一致的,按照我個人的觀點來看,區別的只是在於實現方式,通過this.propertyName的方式來建立,Jscript自動建立了property或者method的入口,不過從程式的角度而言,還是使用prototype的關鍵字實現比較靈活。
這兩種方法效果基本一致,但是還是有比較重要的區別,即前者是在建構函式裡賦值,後者則是從外部通過原型對象賦值。如果有比較複雜的“類層次”,你就會發現兩者的不同,還有順序問題了 後者是更靈活,不過要知道js是完全動態語言,你甚至可以動態改變constructor,呵呵。
> 另外Javascript也可以和我們C++中那種嵌套聲明的方法來聲明,C++實現的方法如下
> 在Javascript當中,當然不存在class這樣的關鍵字了,所以實現起來有點戲劇性,不過仍然為一個非常巧妙的實現。
> function className(){
> //Property Implement
> this.UserName="blue";
> //Method Implement
> this.Add=new function(){
^^^
作者一個小筆誤,這個new是多餘的。
> }
> //Sub Class Implement
> function SubClassName(){
> this.PropertyName="hi"
> }
>
順帶說一下,作者過分使用了object.prototype.prop = value 的方法,這其實並不好。因為這破壞了“類”的結構。除了直接賦值原型對象來類比繼承,如無必要,不應使用。
> 如上的代碼大致示範了Javascript類中屬性和方法的實現,另外有一點比較困惑,整個class中都是public的,沒有關鍵字private之類的可以控制某些方法是否隱藏,那麼在我們編寫代碼實現的規範中,我看國外一些程式員都是使用_functionName這樣子為函數命的方法來區分,但是在調用過程中實際還可以調用的。
public之類的,只是為了增加語言的可靠性,對於指令碼語言來說,並不是必須。
> 實現了屬性和方法,剩下的就是Event的實現了,我尋找了許多資料,包括整個MSDN關於JScript的參考,都沒有看到一個很好的模型關於事件實現的,後來參考了一些網站編寫HTA(HTML Component,有空我會寫一些相關的文章)的實現,藉助於比較扭曲(我個人認為)的方法可以大致的實現基於事件驅動的功能。大致的思路是這樣子的:
> 1).將所有的事件定義成屬性,只要簡單的聲明就可以
> 2).在需要觸發事件的代碼中判斷事件屬性是否是一個函數,如果是函數,直接執行函數代碼,如果是字串,那麼執行字串函數,通過eval(str)來執行。
> 3) .在類的執行個體當中註冊事件函數。
這一部分不作評論,自己去看DOM 2的事件模型。
> 2。繼承。
> 剛採用了大篇幅的文字去介紹如何?Javascript的各種實現,也就是從邏輯上完成了一個封裝class的實現,從某種意義上來說,class的實現是真正指令碼編程中使用最多的部分,不過如果只是要完成如上的功能,使用VBScript來編寫更能更加清晰,畢竟VBscript提供了class關鍵字,同時提供了public 和private這兩個關鍵字,可以清晰的將公用和私人對象分離,至於事件的實現,也可以採用類似Javascript實現的思路,只是對於函數的引用需要採用GetRef這個函數,具體的用法可以參考scripting reference,MSDN裡頭也有詳細的介紹,而Javascript強大至於在於如下要說的了,雖然具體的東西可能不多。
關於vbs我只說一句:vbs是M$牌的垃圾。
> 上述代碼實現了NewTimer類,從Timer繼承,Javascript沒有使用“:”或者java的public那樣類似的關鍵字,只是通過newclassname.prototype=new baseclass這樣的方法來完成,同時NewTimer實現了getSystemDate的方法,在NewTimer的初始化函數中,我使用了this.base=Timer,是為了引用父類的實現,不過在對於父類其他實現函數的調用,到現在我沒有找到一個確定的方法,是否通過this.base.start()那樣來調用還是其他的,如果有誰比較清楚的,麻煩告訴我,另外在netscape的網站上,我查到有一個特殊的"__proto__"的屬性好像是對於父類的直接引用,不過具體的我也沒有嘗試過,在msdn中也沒有看到對於__proto__的支援。
之所以不是像其他語言那樣繼承,那是因為這根本不是“真正”的繼承,因為js根本沒有“真正”的類,呵呵。prototype的實質就是對原型對象的一個引用。至於調用“父類”方法,其實很簡單,在調用了“父類”的建構函式之後直接調用就是了。至於為什麼用this.base=parent,其實想明白一件事情就很簡單了。parent實際上並不是類,而是建構函式。在“子類”建構函式內部,當時還沒有原型存在(prototype是外部動態賦值的,不是靜態資訊)。所以必須手動的擷取“父類”建構函式,運行一下。
也許有人要問,那既然子類直接運行父類建構函式,幹嗎還要原型?把所有東西寫在建構函式不就結了?嘿嘿,想想作者的“第二種方法”。prototype有個特性,給原型增加一個屬性(包括方法),所有繼承這個原型的對象都會自動添加這個屬性。因此,如果你要用這個特性,還是需要原型的。至少你可以用instanceOf操作符來判斷變數的類型。
__proto__可以被檢測和回溯,從而形成一個原型鏈,這在比較複雜的“類結構”裡是有用的,你可以用它來寫自己的instanceOf。不過這不是ecma規範要求的,也應該僅僅在語言內部使用(你真的需要在子類裡去動態改變父類?)。
> 3。重載
> 或許這個是OOP編程中比較複雜的地方了,在Javascript的實現中有點無奈,也就是通過prototype的方式來完成的,不過因為我不清楚如何調用父類的實現函數,那麼在重載中只能夠重新編寫所有的實現了,另外就是在實現中執行個體化一個父類,然後通過調用它來返回需要的東西。
這個無奈只是作者杞人憂天
執行個體化一個父類對象沒有什麼大不了的,不要拘泥於“正統”的OO觀念。想想看,在原型繼承的時候,你實際上不正是執行個體化一個父類對象,然後賦值給prototype嗎?只是這個並不是一個真正需要的執行個體,而只是一個“模子”,所以也許你可以不用傳給建構函式任何參數。調用“父類”的方法,也並不必然需要執行個體化一個父類對象。回憶一下調用父類建構函式的過程。在調用之後,你就已經獲得了所有的通過父類建構函式初始化的屬性和方法。要幹什麼隨便你了。
> Javascript中所有的對象都是從Object繼承下來的,object提供了toString()的方法,也就是說如果調用alert(objInstance)這樣的過程,實際上是調用了alert(objInstance.toString())的方法,如果沒有編寫實現,object預設的toString()都是"object object"這樣子的,在許多地方需要重載這個函數的,比如Timer,如果我們希望var ins=new Timer(5);alert(ins)調用得到的是interval的值5,那麼就需要重新編寫toString()方法了
除了toString,你也可以重寫valueOf用來給你的對象返回一個原生類型的值,呵呵。
> 長篇累牘的說了一堆廢話,終於說玩了大致的想法,其實語言只是一個實現工具,重要的在於設計的思想,不妨可以考慮一下,在BITI內部開發一個OpenSource的Project,如果是基於javascript的模型來建立開發平台庫,我希望有人可以參與。通過javascript建立一系列基於Web UI的控制項,目前我在開發過程中也是立足於上述的想法。另外,附上我去年寫的類似HotMail的按鈕那樣的class來源程式,暫時還沒有使用Image Preload,希望有人可以幫我修改一下,如果需要可以啟動並執行版本,給我發送Email:liuruhong@263.net。另外有空我會寫基於Javascript的組件編程和多媒體編程部分,再下來就是XML方面了,希望大家共同進步。
語言是工具,有不同的應用目標。設計思想當然可以共通,特別是對象語言。你大可以用js快速開發一個原型(一般意義上的原型,不是前面的技術術語),然後用java, c++作最終成品。至於用js寫webUI,我要稍微潑一點點冷水,不必太過在意。webUI的問題主要不在於js,而在於瀏覽器本身和其他技術標準。比如mozilla的UI用XUL語言來寫的。而且許多人認為XBL和HTC(都是用js來封裝web控制項)等不是很好的方向(雖然我一度挺喜歡這兩種技術),因為沒有很好的正交分解,把樣式、js等都混淆了。未來的方向還是要靠w3c這個標準化組織給出。所以用js也不必寫很複雜的類結構(畢竟指令碼語言是要給人們輕鬆的),更多精力花在DOM和相容性上吧。
最後說一下,netscape還在繼續開發js 2.0(好幾年了),ecma也跟著同時作edition 4(M$就不清楚其動向,而且其jscript還沒有完全實現edition 3),這個版本裡好像加入了class,public等關鍵字,可能會有很大的變化。有興趣的人可以去看:http://www.mozilla.org/js/language/js20/index.html