亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

全面理解面向對象的 JavaScript

系統 1965 0

前言

?

當今 JavaScript 大行其道,各種應用對其依賴日深。web 程序員已逐漸習慣使用各種優秀的 JavaScript 框架快速開發 Web 應用,從而忽略了對原生 JavaScript 的學習和深入理解。所以,經常出現的情況是,很多做了多年 JS 開發的程序員對閉包、函數式編程、原型總是說不清道不明,即使使用了框架,其代碼組織也非常糟糕。這都是對原生 JavaScript 語言特性理解不夠的表現。要掌握好 JavaScript,首先一點是必須摒棄一些其他高級語言如 Java、C# 等類式面向對象思維的干擾,全面地從函數式語言的角度理解 JavaScript 原型式面向對象的特點。把握好這一點之后,才有可能進一步使用好這門語言。本文適合群體:使用過 JS 框架但對 JS 語言本質缺乏理解的程序員,具有 Java、C++ 等語言開發經驗,準備學習并使用 JavaScript 的程序員,以及一直對 JavaScript 是否面向對象模棱兩可,但希望知道真相的 JS 愛好者。

?

重新認識面向對象

?

為了說明 JavaScript 是一門徹底的面向對象的語言,首先有必要從面向對象的概念著手 , 探討一下面向對象中的幾個概念:

?

  • 一切事物皆對象
  • 對象具有封裝和繼承特性
  • 對象與對象之間使用消息通信,各自存在信息隱藏

?

以這三點做為依據,C++ 是半面向對象半面向過程語言,因為,雖然他實現了類的封裝、繼承和多態,但存在非對象性質的全局函數和變量。Java、C# 是完全的面向對象語言,它們通過類的形式組織函數和變量,使之不能脫離對象存在。但這里函數本身是一個過程,只是依附在某個類上。

?

?

然而,面向對象僅僅是一個概念或者編程思想而已,它不應該依賴于某個語言存在。比如 Java 采用面向對象思想構造其語言,它實現了類、繼承、派生、多態、接口等機制。但是這些機制,只是實現面向對象編程的一種手段,而非必須。換言之,一門語言可 以根據其自身特性選擇合適的方式來實現面向對象。所以,由于大多數程序員首先學習或者使用的是類似 Java、C++ 等高級編譯型語言(Java 雖然是半編譯半解釋,但一般做為編譯型來講解),因而先入為主地接受了“類”這個面向對象實現方式,從而在學習腳本語言的時候,習慣性地用類式面向對象語 言中的概念來判斷該語言是否是面向對象語言,或者是否具備面向對象特性。這也是阻礙程序員深入學習并掌握 JavaScript 的重要原因之一。

?

?

實際上,JavaScript 語言是通過一種叫做 原型( prototype 的方式來實現面向對象編程的。下面就來討論 基于類的( class-based )面向對象 基于原型的 ( prototype-based ) 面向對象 這兩種方式在構造客觀世界的方式上的差別。

?

基于類的面向對象和基于原型的面向對象方式比較

?

在基于類的面向對象方式中, 對象( object 依靠 類( class 來產生。而在基于原型的面向對象方式中, 對象( object 則是依靠 構造器( constructor 利用 原型( prototype 構造出來的。舉個客觀世界的例子來說明二種方式認知的差異。例如工廠造一輛車,一方面,工人必須參照一張工程圖紙,設計規定這輛車應該如何制造。這里的工程圖紙就好比是語言中的 類 ( class ) ,而車就是按照這個 類( class 制造出來的;另一方面,工人和機器 ( 相當于 constructor) 利用各種零部件如發動機,輪胎,方向盤 ( 相當于 prototype 的各個屬性 ) 將汽車構造出來。

?

?

事實上關于這兩種方式誰更為徹底地表達了面向對象的思想,目前尚有爭論。但筆者認為原型式面向對象是一種更為徹底的面向對象方式,理由如下:

?

首先,客觀世界中的對象的產生都是其它實物對象構造的結果,而抽象的“圖紙”是不能產生“汽車”的,也就是說,類是一個抽象概念而并非實體,而對象的產生是一個實體的產生;

?

其次,按照一切事物皆對象這個最基本的面向對象的法則來看,類 (class) 本身并不是一個對象,然而原型方式中的構造器 (constructor) 和原型 (prototype) 本身也是其他對象通過原型方式構造出來的對象。

?

?

再次,在類式面向對象語言中,對象的狀態 (state) 由對象實例 (instance) 所持有,對象的行為方法 (method) 則由聲明該對象的類所持有,并且只有對象的結構和方法能夠被繼承;而在原型式面向對象語言中,對象的行為、狀態都屬于對象本身,并且能夠一起被繼承( 參考資源 ),這也更貼近客觀實際。

?

?

最后,類式面向對象語言比如 Java,為了彌補無法使用面向過程語言中全局函數和變量的不便,允許在類中聲明靜態 (static) 屬性和靜態方法。而實際上,客觀世界不存在所謂靜態概念,因為一切事物皆對象!而在原型式面向對象語言中,除內建對象 (build-in object) 外,不允許全局對象、方法或者屬性的存在,也沒有靜態概念。所有語言元素 (primitive) 必須依賴對象存在。但由于函數式語言的特點,語言元素所依賴的對象是隨著運行時 (runtime) 上下文 (context) 變化而變化的,具體體現在 this 指針的變化。正是這種特點更貼近 “萬物皆有所屬,宇宙乃萬物生存之根本”的自然觀點。在 程序清單 1 window 便類似與宇宙的概念。


清單 1. 對象的上下文依賴

       <script> 
 var str = "我是一個 String 對象 , 我聲明在這里 , 但我不是獨立存在的!"
 var obj = { des: "我是一個 Object 對象 , 我聲明在這里,我也不是獨立存在的。" }; 
 var fun = function() { 
    console.log( "我是一個 Function 對象!誰調用我,我屬于誰:", this ); 
 }; 

 obj.fun = fun; 

 console.log( this === window );     // 打印 true 
 console.log( window.str === str );  // 打印 true 
 console.log( window.obj === obj );  // 打印 true 
 console.log( window.fun === fun );  // 打印 true 
 fun();                              // 打印 我是一個 Function 對象!誰調用我,我屬于誰:window 
 obj.fun();                          // 打印 我是一個 Function 對象!誰調用我,我屬于誰:obj 
 fun.apply(str);                   // 打印 我是一個 Function 對象!誰調用我,我屬于誰:str 
 </script> 
    

?

在接受了面向對象存在一種叫做基于原型實現的方式的事實之后,下面我們就可以來深入探討 ECMAScript 是如何依據這一方式構造自己的語言的。

?

最基本的面向對象

?

ECMAScript 是一門徹底的面向對象的編程語言( 參考資源 ),JavaScript 是其中的一個變種 (variant)。它提供了 6 種基本數據類型,即 Boolean、Number、String、Null、Undefined、Object。為了實現面向對象, ECMAScript 設計出了一種非常成功的數據結構 - JSON(JavaScript Object Notation), 這一經典結構已經可以脫離語言而成為一種廣泛應用的數據交互格式 ( 參考資源 )。

?

?

應該說,具有基本數據類型和 JSON 構造語法的 ECMAScript 已經基本可以實現面向對象的編程了。開發者可以隨意地用 字面式聲明( literal notation 方式來構造一個對象,并對其不存在的屬性直接賦值,或者用 delete 將屬性刪除 ( 注:JS 中的 delete 關鍵字用于刪除對象屬性,經常被誤作為 C++ 中的 delete,而后者是用于釋放不再使用的對象 ),如 程序清單 2


清單 2. 字面式 (literal notation) 對象聲明

       var person = { 
    name: “張三”, 
    age: 26, 
    gender: “男”, 
    eat: function( stuff ) { 
        alert( “我在吃” + stuff ); 
    } 
 }; 
 person.height = 176; 
 delete person[ “age” ]; 
    

?

在實際開發過程中,大部分初學者或者對 JS 應用沒有太高要求的開發者也基本上只用到 ECMAScript 定義的這一部分內容,就能滿足基本的開發需求。然而,這樣的代碼復用性非常弱,與其他實現了繼承、派生、多態等等的類式面向對象的強類型語言比較起來顯得 有些干癟,不能滿足復雜的 JS 應用開發。所以 ECMAScript 引入原型來解決對象繼承問題。

?

使用函數構造器構造對象

?

除了 字面式聲明( literal notation 方式之外,ECMAScript 允許通過 構造器(constructor) 創建對象。每個構造器實際上是一個 函數( function ) 對象 , 該函數對象含有一個“prototype”屬性用于實現 基于原型的繼承 prototype-based inheritance 共享屬性( shared properties 對象可以由“new 關鍵字 + 構造器調用”的方式來創建,如 程序清單 3


清單 3. 使用構造器 (constructor) 創建對象

       // 構造器 Person 本身是一個函數對象
 function Person() { 
	 // 此處可做一些初始化工作
 } 
 // 它有一個名叫 prototype 的屬性
 Person.prototype = { 
    name: “張三”, 
    age: 26, 
    gender: “男”, 
    eat: function( stuff ) { 
        alert( “我在吃” + stuff ); 
    } 
 } 
 // 使用 new 關鍵字構造對象
 var p = new Person(); 
    

?

由于早期 JavaScript 的發明者為了使這門語言與大名鼎鼎的 Java 拉上關系 ( 雖然現在大家知道二者是雷鋒和雷鋒塔的關系 ),使用了 new 關鍵字來限定構造器調用并創建對象,以使其在語法上跟 Java 創建對象的方式看上去類似。但需要指出的是,這兩門語言的 new 含 義毫無關系,因為其對象構造的機理完全不同。也正是因為這里語法上的類似,眾多習慣了類式面向對象語言中對象創建方式的程序員,難以透徹理解 JS 對象原型構造的方式,因為他們總是不明白在 JS 語言中,為什么“函數名可以作為類名”的現象。而實質上,JS 這里僅僅是借用了關鍵字 new,僅此而已;換句話說,ECMAScript 完全可以用其它 new 表達式來用調用構造器創建對象。

?

徹底理解原型鏈 (prototype chain)

?

在 ECMAScript 中,每個由構造器創建的對象擁有一個指向構造器 prototype 屬性值的 隱式引用( implicit reference ,這個引用稱之為 原型( prototype 。進一步,每個原型可以擁有指向自己原型的 隱式引用 (即該原型的原型),如此下去,這就是所謂的 原型鏈( prototype chain 參考資源 )。在具體的語言實現中,每個對象都有一個 __proto__ 屬性 來實現對原型的 隱式引用 程序清單 4 說明了這一點。


清單 4. 對象的 __proto__ 屬性和隱式引用

       function Person( name ) { 
    this.name = name; 
 } 
 var p = new Person(); 
 // 對象的隱式引用指向了構造器的 prototype 屬性,所以此處打印 true 
 console.log( p.__proto__ === Person.prototype ); 

 // 原型本身是一個 Object 對象,所以他的隱式引用指向了
 // Object 構造器的 prototype 屬性 , 故而打印 true 
 console.log( Person.prototype.__proto__ === Object.prototype ); 

 // 構造器 Person 本身是一個函數對象,所以此處打印 true 
 console.log( Person.__proto__ === Function.prototype ); 
    

?

有了 原型鏈 ,便可以定義一種所謂的 屬性隱藏機制 ,并通過這種機 制實現繼承。ECMAScript 規定,當要給某個對象的屬性賦值時,解釋器會查找該對象原型鏈中第一個含有該屬性的對象(注:原型本身就是一個對象,那么原型鏈即為一組對象的鏈。對象的 原型鏈中的第一個對象是該對象本身)進行賦值。反之,如果要獲取某個對象屬性的值,解釋器自然是返回該對象原型鏈中首先具有該屬性的對象屬性值。 圖 1 說名了這中隱藏機制:


圖 1. 原型鏈中的屬性隱藏機制
圖 1. 原型鏈中的屬性隱藏機制

在圖 1 中,object1->prototype1->prototype2 構成了 對象 object1 的原型鏈,根據上述屬性隱藏機制,可以清楚地看到 prototype1 對象中的 property4 屬性和 prototype2 對象中的 property3 屬性皆被隱藏。理解了原型鏈,那么將非常容易理解 JS 中基于原型的繼承實現原理, 程序清單 5 是利用原型鏈實現繼承的簡單例子。


清單 5. 利用原型鏈 Horse->Mammal->Animal 實現繼承

       // 聲明 Animal 對象構造器
 function Animal() { 
 } 
 // 將 Animal 的 prototype 屬性指向一個對象,
 // 亦可直接理解為指定 Animal 對象的原型
 Animal.prototype = { 
    name: animal", 
    weight: 0, 
    eat: function() { 
        alert( "Animal is eating!" ); 
    } 
 } 
 // 聲明 Mammal 對象構造器
 function Mammal() { 
    this.name = "mammal"; 
 } 
 // 指定 Mammal 對象的原型為一個 Animal 對象。
 // 實際上此處便是在創建 Mammal 對象和 Animal 對象之間的原型鏈
 Mammal.prototype = new Animal(); 

 // 聲明 Horse 對象構造器
 function Horse( height, weight ) { 
    this.name = "horse"; 
    this.height = height; 
    this.weight = weight; 
 } 
 // 將 Horse 對象的原型指定為一個 Mamal 對象,繼續構建 Horse 與 Mammal 之間的原型鏈
 Horse.prototype = new Mammal(); 

 // 重新指定 eat 方法 , 此方法將覆蓋從 Animal 原型繼承過來的 eat 方法
 Horse.prototype.eat = function() { 
    alert( "Horse is eating grass!" ); 
 } 
 // 驗證并理解原型鏈
 var horse = new Horse( 100, 300 ); 
 console.log( horse.__proto__ === Horse.prototype ); 
 console.log( Horse.prototype.__proto__ === Mammal.prototype ); 
 console.log( Mammal.prototype.__proto__ === Animal.prototype ); 
    

?

理解清單 5 中對象原型繼承邏輯實現的關鍵在于 Horse.prototype = new Mammal() 和 Mammal.prototype = new Animal() 這兩句代碼。首先,等式右邊的結果是構造出一個臨時對象,然后將這個對象賦值給等式左邊對象的 prototype 屬性。也就是說將右邊新建的對象作為左邊對象的原型。讀者可以將這兩個等式替換到相應的程序清單 5 代碼最后兩行的等式中自行領悟。

?

JavaScript 類式繼承的實現方法

?

從代碼清單 5 可以看出,基于原型的繼承方式,雖然實現了代碼復用,但其行文松散且不夠流暢,可閱讀性差,不利于實現擴展和對源代碼進行有效地組織管理。不得不承認,類 式繼承方式在語言實現上更具健壯性,且在構建可復用代碼和組織架構程序方面具有明顯的優勢。這使得程序員們希望尋找到一種能夠在 JavaScript 中以類式繼承風格進行編碼的方法途徑。從抽象的角度來講,既然類式繼承和原型繼承都是為實現面向對象而設計的,并且他們各自實現的載體語言在計算能力上是 等價的 ( 因為圖靈機的計算能力與 Lambda 演算的計算能力是等價的 ),那么能不能找到一種變換,使得原型式繼承語言通過該變換實現具有類式繼承編碼的風格呢?

?

?

目前一些主流的 JS 框架都提供了這種轉換機制,也即類式聲明方法,比如 Dojo.declare()、Ext.entend() 等等。用戶使用這些框架,可以輕易而友好地組織自己的 JS 代碼。其實,在眾多框架出現之前,JavaScript 大師 Douglas Crockford 最早利用三個函數對 Function 對象進行擴展,實現了這種變換,關于它的實現細節可以( 參考資源 )。此外還有由 Dean Edwards 實現的著名的 Base.js( 參考資源 )。值得一提的是,jQuery 之父 John Resig 在搏眾家之長之后,用不到 30 行代碼便實現了自己的 Simple Inheritance 。使用其提供的 extend 方法聲明類非常簡單。 程序清單 6 是使用了 Simple Inheritance 庫實現類的聲明的例子。其中最后一句打印輸出語句是對 Simple Inheritance 實現類式繼承的最好說明。


清單 6. 使用 Simple Inheritance 實現類式繼承

       // 聲明 Person 類
 var Person = Class.extend( { 
    _issleeping: true, 
    init: function( name ) { 
        this._name = name; 
    }, 
    isSleeping: function() { 
        return this._issleeping; 
    } 
 } ); 
 // 聲明 Programmer 類,并繼承 Person 
 var Programmer = Person.extend( { 
    init: function( name, issleeping ) { 
        // 調用父類構造函數
        this._super( name ); 
        // 設置自己的狀態
        this._issleeping = issleeping; 
    } 
 } ); 
 var person = new Person( "張三" ); 
 var diors = new Programmer( "張江男", false ); 
 // 打印 true 
 console.log( person.isSleeping() ); 
 // 打印 false 
 console.log( diors.isSleeping() ); 
 // 此處全為 true,故打印 true 
 console.log( person instanceof Person && person instanceof Class 
    && diors instanceof Programmer && 
    diors instanceof Person && diors instanceof Class ); 
    

?

如果您已對原型、函數構造器、閉包和基于上下文的 this 有了充分的理解,那么理解 Simple Inheritance 的實現原理也并非相當困難。從本質上講, var Person = Class.extend(...) 該 語句中,左邊的 Person 實際上是獲得了由 Class 調用 extend 方法返回的一個構造器,也即一個 function 對象的引用。順著這個思路,我們繼續介紹 Simple Inheritance 是如何做到這一點,進而實現了由原型繼承方式到類式繼承方式的轉換的。 圖 2 是 Simple Inheritance 的源碼及其附帶注釋。為了方便理解,用中文對代碼逐行補充說明。


圖 2.Simple Inheritance 源碼解析
圖 2.Simple Inheritance 源碼解析

拋開代碼第二部分,整體連貫地考察第一和第三部分會發現,extend 函數的根本目的就是要構造一個具有新原型屬性的新構造器。我們不禁感嘆 John Resig 的大師手筆及其對 JS 語言本質把握的細膩程度。至于 John Resig 是如何想到這樣精妙的實現方法,感興趣的讀者可以閱讀本文 ( 參考資源 ),其中有詳細介紹關于最初設計 Simple Inheritance 的思維過程。

?

JavaScript 私有成員實現

?

到此為止,如果您任然對 JavaScript 面向對象持懷疑態度,那么這個懷疑一定是,JavaScript 沒有實現面向對象中的信息隱藏,即私有和公有。與其他類式面向對象那樣顯式地聲明私有公有成員的方式不同,JavaScript 的信息隱藏就是靠閉包實現的。見 程序清單 7 :


清單 7. 使用閉包實現信息隱藏

       // 聲明 User 構造器
 function User( pwd ) { 
    // 定義私有屬性
    var password = pwd; 
    // 定義私有方法 
    function getPassword() { 
        // 返回了閉包中的 password 
        return password; 
    } 
    // 特權函數聲明,用于該對象其他公有方法能通過該特權方法訪問到私有成員
    this.passwordService = function() { 
        return getPassword(); 
    } 
 } 
 // 公有成員聲明
 User.prototype.checkPassword = function( pwd ) { 
    return this.passwordService() === pwd; 
 }; 
 // 驗證隱藏性
 var u = new User( "123456" ); 
 // 打印 true 
 console.log( u.checkPassword( "123456" ) ); 
 // 打印 undefined 
 console.log( u.password ); 
 // 打印 true 
 console.log( typeof u.gePassword === "undefined" ); 
    

?

JavaScript 必須依賴閉包實現信息隱藏,是由其函數式語言特性所決定的。本文不會對函數式語言和閉包這兩個話題展開討論,正如上文默認您理解 JavaScript 中基于上下文的 this 一樣。關于 JavaScript 中實現信息隱藏, Douglas Crockford 在《 Private members in JavaScript 》( 參考資源 )一文中有更權威和詳細的介紹。

?

結束語

?

JavaScript 被認為是世界上最受誤解的編程語言,因為它身披 c 語言家族的外衣,表現的卻是 LISP 風格的函數式語言特性;沒有類,卻實也徹底實現了面向對象。要對這門語言有透徹的理解,就必須扒開其 c 語言的外衣,從新回到函數式編程的角度,同時摒棄原有類的面向對象概念去學習領悟它。隨著近些年來 Web 應用的普及和 JS 語言自身的長足發展,特別是后臺 JS 引擎的出現 ( 如基于 V8 的 NodeJS 等 ),可以預見,原來只是作為玩具編寫頁面效果的 JS 將獲得更廣闊發展天地。這樣的發展趨勢,也對 JS 程序員提出了更高要求。只有徹底領悟了這門語言,才有可能在大型的 JS 項目中發揮她的威力。

?

參考資料

學習

討論

加入 developerWorks 中文社區 。查看開發人員推動的博客、論壇、組和維基,并與其他 developerWorks 用戶交流。

?

原文: http://www.ibm.com/developerworks/cn/web/1304_zengyz_jsoo/index.html?ca=drs-#major6

?

?

全面理解面向對象的 JavaScript


更多文章、技術交流、商務合作、聯系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 亚洲精品一区二区四季 | 成人在线天堂 | 精品久久久久久无码中文字幕 | 在线观看日本中文字幕 | 久久精品国产免费看久久精品 | 97看片网| 久久国产精品成人免费 | 久热国产视频 | 伊人丁香狠狠色综合久久 | 国产男女性特黄录像 | 亚洲精品9999久久久久 | 久9视频这里只有精品8 | 国产这里只有精品 | 久热re国产手机在线观看 | 91成人免费观看网站 | 性色黄 | 就要爱综合 | 看片久久| 新香蕉视频在线 | 精品亚洲永久免费精品 | 欧美黄色网址 | 自拍偷自拍亚洲精品被多人伦好爽 | 五月婷婷视频 | 欧美123| 亚洲人成在线免费观看 | 在线免费观看a视频 | 日韩欧美理论 | 九九视频在线观看视频6偷拍 | 在线欧美视频免费观看国产 | 色综合天天综合网国产成人 | 久久亚洲国产最新网站 | 日本高清在线观看天码888 | 亚洲综合伦理 | 亚洲欧美日韩国产综合 | 亚洲精品一区二区三区婷婷月 | 国产日韩精品一区二区在线观看 | 成年女人毛片免费观看中文w | 国产精品永久免费视频 | 成人性生活视频 | 日本高清不卡二区 | 在线亚洲精品国产波多野结衣 |