JavaScript 面向對象編程.doc_第1頁
JavaScript 面向對象編程.doc_第2頁
JavaScript 面向對象編程.doc_第3頁
JavaScript 面向對象編程.doc_第4頁
JavaScript 面向對象編程.doc_第5頁
已閱讀5頁,還剩9頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

JavaScript 面向對象編程, 第一部分: 繼承作者:lzlhero 發布:heiyeluren 時間:2005-08-25我們將向你展示 JavaScript 如何實現面向對象的語言中的: 繼承. 同時, 這些例子將向你展示如何實現類的封裝. 在此, 我們不會討論多態實現.雖然 JavaScript 是腳本語言, 但它所支持的面向對象編程也是非常強大的. 雖然它沒有類和實例, 但它有對象, 原型和隱式的繼承. 我們將會解釋如何模擬繼承及其超類與子類之間關系的形式. 原型是理解繼承概念的關鍵, 我們將會教你如何建立原型, 如何檢測一個對象是否是另外一個對象的原型, 及其 JavaScript 的模型與 Java 面向對象編程之間的區別. 我們同樣會向你展示如何檢測對象所包含的各種屬性的方法. 在另外一篇文章里, 我還會詳細地講解有關 原型鏈 (prototype chain) 的知識.本文大量地參考了 W 中 Object-Oriented Programming with JavaScript, Part I: Inheritance 的內容, 許多內容我進行了詳細的測試和再探討, 以保證內容不會有太大的失誤.原文地址: /js/column79/面向對象語言的特點面向對象設計是基于以下 3 個主要原理的: 封裝, 繼承和多態. 說某種程序語言是支持 OO (面向對象) 設計的, 只有在它的語法中支持以上 3 個概念才可以這么說. 這種語言應該為你提供某些方法, 以使你能很輕松地定義和使用這些范例. 封裝涉及到了將某個對象變成一個 黑盒子的概念. 當你使用某個對象時, 你不用知道它內部是如何工作的, 你也不必理解對象是如何工作的. 這個對象只需將它絕對有用的信息以接口方式提供出來. 此對象應該給你提供友好的接口, 來讓你可以使用其有限的屬性集和方法集. 封裝還有一層意思, 那就是說某個對象包含了它需要的每一樣東西, 這包括數據和對于它的操作. 封裝的概念非常的強大, 因為它允許將一個大的軟件項目有效地分配給每個開發人員, 對于團隊中的每個人, 他們只需要關注自己所實現的對象, 而不需要太多地關注于別人的實現. 開發項目中的開銷使得開發團隊中成員與接口的數量按指數級別增長. 封裝是自 軟件危機 以來最受歡迎的 OO 設計理念.軟件的復用是 OO 設計思想中另外一個重要的特點. 在軟件體系中實現此思想的主要方法就是繼承. 類就是定義對象的功能. 超類是某個新類, 或者說是子類被建立的來源類. 一個子類從它的超類中繼承了所的方法和屬性. 實際上, 所有的子類都是被自動地生成的, 因此節省了大量的工作. 你不需要一個一個地定義這些子類. 當然, 你可以重載那些繼承下來的方法和屬性. 事實上, 誰也沒有指出哪個子類要建立得和其超類一模一樣, 除非你沒有重載任何的屬性和方法.多態可能是這個 3 個概念中最復雜的一個了. 其本質上是說, 每個對象都可以處理各種不同的數據類型. 你不必為處理不同的數據類型而建立不同的類. 其典型的例子就是畫圖的類, 你不必為實現畫圓, 畫矩形, 畫橢圓而編寫不同的類. 你可以建立一個足夠聰明的類來調用特定的方法來操作特定的形狀.通過函數實現繼承雖然 JavaScript 不支持顯示繼承操作符, 但你可以通過其實方式實現隱式繼承. 對于實現類的繼承, 有 2 種比較常用的方式. 第一種將某個類定義成子類的方法是, 通過在負責定義子類函數的內部調用超類的構造函數. 看下面的示例:/ 超類構造函數function superClass() this.bye = superBye; this.hello = superHello;/ 子類構造函數function subClass() this.inheritFrom = superClass; this.inheritFrom(); this.bye = subBye;function superHello() return Hello from superClass; function superBye() return Bye from superClass;function subBye() return Bye from subClass;/ 測試構造特性的函數function printSub() var newClass = new subClass(); alert(newClass.bye(); alert(newClass.hello();當你運行上面的 printSub 函數時, 它會依次執行 subBuy 和 superHello 函數. 我們可以看到, bye 和 hello 方法最先在 superClass 中被定義了. 然而, 在 subClass 中, bye 方法又被重載了, subClass 構造函數頭兩行的功能只是做了一個簡單的原始的繼承操作, 但它是通過顯示執行 inheritFrom 方法來完成的繼承操作. 繼承的過程先是將 superClass 的對象原型賦給 subClass 下的 inheritFrom 方法, 然后在執行完 superClass 的構造函數后, superClass 的屬性就被自動地加到了 subClass 的屬性列表中.這主要是由于在 subClass 中通過 this 來調用的 inheritFrom (也就是 superClass) 構造函數造成的, 通過此種方式調用 superClass 構造函數時, JavaScript 解釋器會把 superClass 中的 this 與 subClass 中的 this 理解成位于同一個作用域下的 this 關鍵字, 所以就產生了繼承的效果.另外, 需要說明的是, 對于任何一個實例化的對象, 你任意地為它添加屬性或方法, 如下所示: var newClass = new subClass(); newClass.addprop = added property to instance object;很明顯, 通過此種方式添加的屬性和方法只對當前實例化對象有效, 不會影響所有的同類型對象實例. 無疑, 它是你創造的一個獨一無二的對象實例.通過原型實現繼承第二種, 也是更強大的方法是通過建立一個超類對象, 然后將其賦值給子類對象的 prototype 屬性, 以此方式來建立子類的繼承. 假設我們的超類是 superClass, 子類是 subClass. 其 prototype 的賦值格式如下:subCtotype = new superClass;對于原型繼承的實現方式, 讓我們剛前面的代碼改寫一下, 示例如下:/ 超類構造函數function superClass() this.bye = superBye; this.hello = superHello;/ 子類構造函數function subClass() this.bye = subBye;subCtotype = new superClass;function superHello() return Hello from superClass; function superBye() return Bye from superClass;function subBye() return Bye from subClass;/ 測試構造特性的函數function printSub() var newClass = new subClass(); alert(newClass.bye(); alert(newClass.hello();我們可以看到, 除了將前面第一種繼承方式中 subClass 中的前 2 行內容, 換成函數外的 prototype 賦值語句之外, 沒有其它任何的變化, 但代碼的執行效果和前面是一樣的.為已經建立的對象添加屬性通過原型實現的繼承比通過函數實現的繼承更好, 因為它支持動態繼承. 你可以在構造函數已經完成之后, 再通過 prototype 屬性定義超類的其它方法和屬性, 并且其下的子類對象會自動地獲得新的方法和屬性. 下面是示例, 你可以看到它的效果.function superClass() this.bye = superBye; this.hello = superHello;function subClass() this.bye = subBye;subCtotype = new superClass;function superHello() return Hello from superClass; function superBye() return Bye from superClass;function subBye() return Bye from subClass;var newClass = new subClass();/*/ 動態添加的 blessyou 屬性superCtotype.blessyou = superBlessyou;function superBlessyou() return Bless You from SuperClass;/*/function printSub() alert(newClass.bye(); alert(newClass.hello(); alert(newClass.blessyou();這就是我們經常看到的為內部對象, 如 String, Math 等再添加其它屬性和方法的技巧. 對于任何的內部對象和自定義對象, 你都也可以通過 prototype 來重載其下的屬性和方法. 那么在調用執行時, 它將調用你所定義的方法和屬性. 下面是示例:/ 為內部 String 對象添加方法Stotype.myMethod = function() return my define method;/ 為內部 String 對象重載方法Stotype.toString = function() return my define toString method;var myObj = new String(foo);alert(myObj.myMethod();alert(myObj);alert(foo.toString();另外需要注意的是, 所有 JavaScript 內部對的 prototype 屬性都是只讀的. 你可以像上面那樣為內部對象的原型添加或重載屬性和方法,但不能更改該內部對象的 prototype 原型. 然而, 自定義對象可以被賦給新的原型. 也就是說, 像下面這樣做是沒有意思的.function Employee() this.dept = HR; this.manager = John Johnson;Stotype = new Employee;var myString = new String(foo);上面的程序在運行之后不會報錯, 但顯然, 如果你調用 myString.dept 將會得到一個非定義的值.另外, 一個經常使用的是 prototype 下的 isPrototypeOf() 方法, 它主要用來判斷指定對象是否存在于另一個對象的原型鏈中. 語法如下:totype.isPrototypeOf(0bject2);上面的格式是用來判斷 Object2 是否出現 Object1 的原型鏈中. 示例如下:function Person() = Rob Roberson; this.age = 31;function Employee() this.dept = HR; this.manager = John Johnson;Etotype = new Person();var Ken = new Employee();當執行 Etotype.isPrototypeOf(Ken), Ptotype.isPrototypeOf(Ken) 和 Ototype.isPrototypeOf(Ken) 時, 結果都會返回 true.用于 Netscape 下的特定繼承檢測在 Netscape 瀏覽器 4.x 到 6, 及其 Mozilla 系列瀏覽中, JavaScript 將對象間的原型關系存儲在一個特殊的內部屬性對象中, _proto_ (前后是 2 個下劃線). 下面是一個示例:function Shape() this.borderWidth = 5;function Square() this.edge = 12;Stotype = new Shape;myPicture = new Square;alert(myPicture._proto_);alert(myPicture.borderWidth);由于腳本執行過 Stotype = new Shape 語句, 所以 myPicture 具有了一個指向 Shape 對象的內部屬性 _proto_. 在腳本的執行過程中, 當要獲取對象的某個屬性值, 并且此對象是通過原型賦值而建立的某個對象, 在自身并沒有對某個屬性進行定義時, JavaScript 解析器會查看它的 _proto_ 屬性對象, 也就它的原型對象, 然后枚舉其原型中的所有屬性, 而得出的結果要么是有這個屬性, 要么是沒有這個屬性. 如果沒有此屬性, 再枚舉原型對象下面的原型對象, 直到此過程真正的結束. 而所有的這些 JavaScript 引擎內部的操作, 我們是不會知道的, 下面的內容就是對這個問題的解釋.其實, 對于所有的自定義對象, 無論它有沒有使用過 prototype 賦值操作, 它都具有一個 _proto_ 內部對象. 而如果某個對象是通過多層 prototype 繼承 來的, 所有的 繼承 而來的屬性卻可以通過簡單的一層循環遍歷出來, 而不需要使用什么遞歸算法, 因為 JavaScript 引擎自動給我們做了. 示例如下:function Shape() this.borderWidth = 5;function Square() this.edge = 12;function RoundSquare() this.radio = 0.5;Stotype = new Shape;RoundStotype = new Square;var myPicture = new RoundSquare;for (property in myPicture._proto_) alert(property);我們或者還可以通過更改后面的循環, 來遍歷某個子類對象繼承來的所有屬性, 如下:for (property in RoundStotype) alert(property);如果你不怕麻煩, 我們甚至還可以通過級連的方式, 取出其構造函數中定義的原始屬性值.alert(myPicture._proto_._proto_.borderWidth);無論你是否修改過此屬性值, 通過上面語句所取出的屬性值都是原始定義值. 讓我們沿著這個思路再往下看, 下面的代碼涉及到另外一個問題, 這個問題和原型鏈 (prototype chain) 有關. 代碼如下:function State() function City() Ctotype = new State;function Street() Stotype = new City;var UniversityAvenue = new Street();function tryIt() alert(UniversityAvenue._proto_= Stotype); alert(UniversityAvenue._proto_._proto_= Ctotype); alert(UniversityAvenue._proto_._proto_._proto_ = Stotype); alert(UniversityAvenue._proto_._proto_._proto_. _proto_= Ototype); alert(UniversityAvenue._proto_._proto_._proto_. _proto_._proto_= null);當執行 tryIt 函數時, 所有的顯示均為 true. 也就是說, 子類對象的 prototype._proto_ 總是等于超類對象的 prototype 屬性; 超類對象的 prototype._proto_ 總是等于 Ototype; Ototype._proto_ 總是為 null; 而實例對象的 _proto_ 總是等于其類對象的 prototype, 這就是為什么任何自定義對象都具有 _proto_ 屬性的原因. 對于上面的敘述, 其對應的代碼如下:Stotype._proto_ = Ctotype / trueStotype._proto_ = Ototype / trueOtotype._proto_ = null / trueUniversityAvenue._proto_ = Stotype / true模擬實現 instanceOf 函數根據上一節的內容, 我們了解了有關 Netscape 所支持的 _proto_ 特性的內容. 這一節, 我們將利用此特性來創建自己的實例對象檢測函數.許多時候, 我們都需要判斷某個對象是否是由某個類來定義的, 在其它的語言里, 你可以通過 instanceOf 函數來實現此判斷. 在 JavaScript 中同樣提供了一個 instanceof 運行符, 而在 _proto_ 的基礎上, 我們完全可以自己定義一個同樣的函數, 雖然這看上去是在重復勞動, 但有助于我們更深刻地了解有關 _proto_ 的知識. 下面的代碼只是用來說明功能, 在實際的應用中, 你不需要重復定義 instanceOf 函數, 使用 instanceof 運算符即可.function instanceOf(object, constructorFunction) while (object != null) if (object = constructorFtotype) return true object = object._proto_; return false;function State() function City() Ctotype = new State;function Street() Stotype = new City;var UniversityAvenue = new Street();function demo() alert(instanceOf(UniversityAvenue, Street) is + instanceOf(UniversityAvenue, Street); alert(instanceOf(UniversityAvenue, City) is + instanceOf(UniversityAvenue, City); alert(instanceOf(UniversityAvenue, State) is + instanceOf(UniversityAvenue, State);你會看到所有的運行結果全部為 true, 其原理和上一節的級連判斷相等如出一轍. 實際證明, 它的運行結果和 instanceof 運行符的運行結果是一致的.你可以通過 constructor 屬性來檢測任意對象的超類, 此屬性返回通過 new 運算符創建新對象時所調用的構造函數, 返回值是 Function 對象類型. 因為 Object 內部對象是支持 constructor 屬性的, 并且有的對象 (包括內部對象和自定義對象) 都是由 Object 繼承而來的, 所以所有的對象都支持此屬性. 讓我們再看一下下面的例子:function Employee() this.dept = HR; this.manager = John Johnson;function printProp() var Ken = new Employee(); alert(Ken.constructor);調用完 printProp 函數之后, 你會看到彈出框中顯示的是 Employee 函數的定義文本, 其實 Ken.constructor 的返回值本身是 Function 對象類型, 而在 alert 時被隱含地調用了 toString 方法. 對于類對象本身, 你同樣可以調用 prototype.constructor 來取出其構造函數.對象的分類和打印JavaScript 支持 3 種主要類型的對象: 內部對象, 宿主對象, 自定義對象, 可能還有特殊的外部對象, 如: ActiveX 對象或 XPCOM 對象. 內部對象被 JavaScript 語言本身所支持, 如: Object, Math, Number 對象等. 所有的內部對象的共同特點是以大寫字母開頭, 并且它們是大小寫敏感的. 如果你想使用數學常量 PI, 必須寫成 Math.PI, 你如果寫成 math.PI, JavaScript 會顯示錯誤. 宿主對象是被瀏覽器支持的, 目的是為了能和被瀏覽的文檔可以交互, 如: document, window 和 frames. 宿主對象的特點是所有對象全部以小寫字母開頭. 因為 JavaScript 本身就是大小寫敏感的, 所以你同樣不能將大小寫搞混. 剩下要說的就只是自定義對象了, 你可以隨便將你的對象定義成小寫或大小寫, 但是一定要符合基本的命名規范. 如下所示, 這就是一個自定義對象:function employee() this.dept = HR; this.manager = John Johnson;function printProp() var ken = new Employee(); for (property in ken) alert(property); 前面我們已經提到過, 所有的內部對象和自定義對象都是從 Object 對象繼承而來的, 它是所有對象的超類對象. 你可建立一個 Object 對象的實例. 如下:var myObject = new Object();Object 類型的對象有許多的屬性和方法, 你可以查看相關的手冊. 上面只是定義了一個最簡單的空對象, 你還可以為 Object 構造函數傳入參數, 它會返回相應類型值的實例化對象. 記住, 返回值的類型是某種對象類型的 (如: String, Number 或 Object). 這種方式和直接通過賦值字符串或數值常量不同, 主要表示在類型方面. 如下所示:var myObject = new Object(foo); / 返回值類型為 objectvar myObject = new String(foo); / 返回值類型為 object, 效果同上與var myObject = foo; / 返回值類型為 string你可以從調試器的 type 列中看出這個細微的差別, 它是簡單類型與對象類型之間的區別. 但是, 你通過 alert 調用是看出不這些內部差別的, 因為在調用 alert 的過程中, 所有的對象類型值都會被自動調用 toString 方法進行字符串類型轉換, 轉換規則在 JavaScript 手冊中有說明. 如果你 alert 的是某個自定義對象, 并且它沒有定義 toString 方法, 那么它的返回值將為 object Object. 對于 Math 對象, 當你查看其 Math.constructor 屬性時, 你會得到一個不同于其它內部對象的內容為 function Object(). 的對象構造函數, 這與其它對象返回 function Function(). 的構造函數很不相同. 原因很簡單, 因為 Math 對象是不能通過 new 運算符進行創建的.另外, 如果傳入 Object 構造函數中的值是一個對象, 它將原封不動地將該對象返回. 記住, 此操作只是一個引用, 而不是復制.請求對象的屬性在前面的示例代碼中, 已經出現過以循環方式枚舉對象屬性的示例. 其實, 通過 for.in 語句, 無論是任何對象和數組, 其下的元素, 屬性和方法都可以遍歷出來. 示例如下:function employee() this.dept = HR; this.manager = John Johnson;function printProp() var ken = new employee(); for (property in ken) alert(property + : + kenproperty); 在遍歷測試過程中, 你會發現, 對于自定義對象和宿主對象一般都可以枚舉出其下的屬性, 而對于內部對象, 幾乎沒有什么屬性可以遍歷出來, 為什么要說幾乎呢? 因為對于 Mozilla 內核的瀏覽和 IE 內核的瀏覽器, 其 JavaScript 引擎有不同, Mozilla 下可以枚舉出部分內容, 而枚舉的原則不得而知.對于每一個對象, 你還可以使用 hasOwnProperty 方法來檢測其是否具有某個屬性或方法. 由于 hasOwnProperty 是 Object 對象下的方法, 因此所有的對象都具有此方法. 但是, 需要注意的是, 此方法只能檢測通過 this 關鍵字定義的成員, 如果某個成員是通過原型鏈定義的, 那么此方法將返回 false. 也就是說, 通過 prototype 繼承來的屬性和方法, 及其通過 prototype 定義的屬性和方法, 都是不能通過 hasOwnProperty 來進行檢測的. 由此, 我們可以看出, 通過 this關鍵字定義的屬性和方法是同對象本身處于同一個地址空間內的; 而通過 prototype 定義的屬性和方法, 是通過所謂的 原型鏈 進行管理的, 其下的的屬性和方法不位于同一個地址空間之間, 當其調用這種屬性或方法時, 必須通過 鏈表 才能索引到其下的某個屬性或方法. 也就說, 調用以原型方式定義的屬性和方法會有一個類似于鏈表的 回溯 操作.和 hasOwnProperty 差不多, 對于對象中的每個屬性, 我們還可以通過 propertyIsEnumerable 來測試它是否可以被枚舉出來. 如下所示:function Employee1() this.dept = HR; this.manager = John Johnson;

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論