JavaScript的面向?qū)ο缶幊袒A(chǔ)
重新認(rèn)識(shí)面向?qū)ο?br /> 為了說(shuō)明 JavaScript 是一門(mén)徹底的面向?qū)ο蟮恼Z(yǔ)言,首先有必要從面向?qū)ο蟮母拍钪?, 探討一下面向?qū)ο笾械膸讉€(gè)概念:
- 一切事物皆對(duì)象
- 對(duì)象具有封裝和繼承特性
- 對(duì)象與對(duì)象之間使用消息通信,各自存在信息隱藏
以這三點(diǎn)做為依據(jù),C++ 是半面向?qū)ο蟀朊嫦蜻^(guò)程語(yǔ)言,因?yàn)?,雖然他實(shí)現(xiàn)了類(lèi)的封裝、繼承和多態(tài),但存在非對(duì)象性質(zhì)的全局函數(shù)和變量。Java、C# 是完全的面向?qū)ο笳Z(yǔ)言,它們通過(guò)類(lèi)的形式組織函數(shù)和變量,使之不能脫離對(duì)象存在。但這里函數(shù)本身是一個(gè)過(guò)程,只是依附在某個(gè)類(lèi)上。
然而,面向?qū)ο髢H僅是一個(gè)概念或者編程思想而已,它不應(yīng)該依賴(lài)于某個(gè)語(yǔ)言存在。比如 Java 采用面向?qū)ο笏枷霕?gòu)造其語(yǔ)言,它實(shí)現(xiàn)了類(lèi)、繼承、派生、多態(tài)、接口等機(jī)制。但是這些機(jī)制,只是實(shí)現(xiàn)面向?qū)ο缶幊痰囊环N手段,而非必須。換言之,一門(mén)語(yǔ)言可以根據(jù)其自身特性選擇合適的方式來(lái)實(shí)現(xiàn)面向?qū)ο?。所以,由于大多?shù)程序員首先學(xué)習(xí)或者使用的是類(lèi)似 Java、C++ 等高級(jí)編譯型語(yǔ)言(Java 雖然是半編譯半解釋?zhuān)话阕鰹榫幾g型來(lái)講解),因而先入為主地接受了“類(lèi)”這個(gè)面向?qū)ο髮?shí)現(xiàn)方式,從而在學(xué)習(xí)腳本語(yǔ)言的時(shí)候,習(xí)慣性地用類(lèi)式面向?qū)ο笳Z(yǔ)言中的概念來(lái)判斷該語(yǔ)言是否是面向?qū)ο笳Z(yǔ)言,或者是否具備面向?qū)ο筇匦?。這也是阻礙程序員深入學(xué)習(xí)并掌握 JavaScript 的重要原因之一。
實(shí)際上,JavaScript 語(yǔ)言是通過(guò)一種叫做 原型(prototype)的方式來(lái)實(shí)現(xiàn)面向?qū)ο缶幊痰摹O旅婢蛠?lái)討論 基于類(lèi)的(class-based)面向?qū)ο蠛?基于原型的 (prototype-based) 面向?qū)ο筮@兩種方式在構(gòu)造客觀世界的方式上的差別。
基于類(lèi)的面向?qū)ο蠛突谠偷拿嫦驅(qū)ο蠓绞奖容^
在基于類(lèi)的面向?qū)ο蠓绞街?,?duì)象(object)依靠 類(lèi)(class)來(lái)產(chǎn)生。而在基于原型的面向?qū)ο蠓绞街?,?duì)象(object)則是依靠 構(gòu)造器(constructor)利用 原型(prototype)構(gòu)造出來(lái)的。舉個(gè)客觀世界的例子來(lái)說(shuō)明二種方式認(rèn)知的差異。例如工廠造一輛車(chē),一方面,工人必須參照一張工程圖紙,設(shè)計(jì)規(guī)定這輛車(chē)應(yīng)該如何制造。這里的工程圖紙就好比是語(yǔ)言中的 類(lèi) (class),而車(chē)就是按照這個(gè) 類(lèi)(class)制造出來(lái)的;另一方面,工人和機(jī)器 ( 相當(dāng)于 constructor) 利用各種零部件如發(fā)動(dòng)機(jī),輪胎,方向盤(pán) ( 相當(dāng)于 prototype 的各個(gè)屬性 ) 將汽車(chē)構(gòu)造出來(lái)。
事實(shí)上關(guān)于這兩種方式誰(shuí)更為徹底地表達(dá)了面向?qū)ο蟮乃枷?,目前尚有?zhēng)論。但筆者認(rèn)為原型式面向?qū)ο笫且环N更為徹底的面向?qū)ο蠓绞?,理由如下?br />
首先,客觀世界中的對(duì)象的產(chǎn)生都是其它實(shí)物對(duì)象構(gòu)造的結(jié)果,而抽象的“圖紙”是不能產(chǎn)生“汽車(chē)”的,也就是說(shuō),類(lèi)是一個(gè)抽象概念而并非實(shí)體,而對(duì)象的產(chǎn)生是一個(gè)實(shí)體的產(chǎn)生;
其次,按照一切事物皆對(duì)象這個(gè)最基本的面向?qū)ο蟮姆▌t來(lái)看,類(lèi) (class) 本身并不是一個(gè)對(duì)象,然而原型方式中的構(gòu)造器 (constructor) 和原型 (prototype) 本身也是其他對(duì)象通過(guò)原型方式構(gòu)造出來(lái)的對(duì)象。
再次,在類(lèi)式面向?qū)ο笳Z(yǔ)言中,對(duì)象的狀態(tài) (state) 由對(duì)象實(shí)例 (instance) 所持有,對(duì)象的行為方法 (method) 則由聲明該對(duì)象的類(lèi)所持有,并且只有對(duì)象的結(jié)構(gòu)和方法能夠被繼承;而在原型式面向?qū)ο笳Z(yǔ)言中,對(duì)象的行為、狀態(tài)都屬于對(duì)象本身,并且能夠一起被繼承(參考資源),這也更貼近客觀實(shí)際。
最后,類(lèi)式面向?qū)ο笳Z(yǔ)言比如 Java,為了彌補(bǔ)無(wú)法使用面向過(guò)程語(yǔ)言中全局函數(shù)和變量的不便,允許在類(lèi)中聲明靜態(tài) (static) 屬性和靜態(tài)方法。而實(shí)際上,客觀世界不存在所謂靜態(tài)概念,因?yàn)橐磺惺挛锝詫?duì)象!而在原型式面向?qū)ο笳Z(yǔ)言中,除內(nèi)建對(duì)象 (build-in object) 外,不允許全局對(duì)象、方法或者屬性的存在,也沒(méi)有靜態(tài)概念。所有語(yǔ)言元素 (primitive) 必須依賴(lài)對(duì)象存在。但由于函數(shù)式語(yǔ)言的特點(diǎn),語(yǔ)言元素所依賴(lài)的對(duì)象是隨著運(yùn)行時(shí) (runtime) 上下文 (context) 變化而變化的,具體體現(xiàn)在 this 指針的變化。正是這種特點(diǎn)更貼近 “萬(wàn)物皆有所屬,宇宙乃萬(wàn)物生存之根本”的自然觀點(diǎn)。
JavaScript 面向?qū)ο蠡A(chǔ)知識(shí)
雖然 JavaScript 本身是沒(méi)有類(lèi)的概念,但它仍然有面向?qū)ο蟮奶匦?,雖然和一般常見(jiàn)的面向?qū)ο笳Z(yǔ)言有所差異。
簡(jiǎn)單的創(chuàng)建一個(gè)對(duì)象的方法如下:
function myObject() { }; JavaScript 中創(chuàng)建對(duì)象的方法一般來(lái)說(shuō)有兩種:函數(shù)構(gòu)造法和字面量法,上面這種屬函數(shù)構(gòu)造法。下面是一個(gè)字面量法的例子: var myObject = { };
如果僅僅需要一個(gè)對(duì)象,而不需要對(duì)象的其它實(shí)例的情況下,推薦用字面量法。如果需要對(duì)象的多個(gè)實(shí)例,則推薦函數(shù)構(gòu)造法。
定義屬性和方法
函數(shù)構(gòu)造法:
function myObject() { this.iAm = 'an object'; this.whatAmI = function() { console.log('I am ' + this.iAm); }; };
字面量法:
var myObject = { iAm : 'an object', whatAmI : function() { console.log('I am ' + this.iAm); } };
以上兩種方法創(chuàng)建的對(duì)象中,都有一個(gè)名為 “iAm” 的屬性,還有一個(gè)名為 “whatAmI” 的方法。屬性是對(duì)象中的變量,方法則是對(duì)象中的函數(shù)。
如何獲取屬性及調(diào)用方法:
var w = myObject.iAm; myObject.whatAmI();
調(diào)用方法的時(shí)候后面一定要加上括號(hào),如果不加括號(hào),那么它只是返回方法的引用而已。
兩種創(chuàng)建對(duì)象方法的區(qū)別
- 函數(shù)構(gòu)造法里面定義屬性和方法的時(shí)候,都要用前綴 this,字面量法不需要。
- 函數(shù)構(gòu)造法給屬性和方法賦值的時(shí)候用的是 =,字面量法用的是 : 。
- 如果有多個(gè)屬性或方法,函數(shù)構(gòu)造法里面用 ; 隔開(kāi),字面量法用 , 隔開(kāi)。
對(duì)于字面量法創(chuàng)建的對(duì)象,可以直接用對(duì)象的引用調(diào)用其屬性或方法:
myObject.whatAmI();
而對(duì)于函數(shù)構(gòu)造法而言,需要?jiǎng)?chuàng)建對(duì)象的實(shí)例,才能調(diào)用其屬性或方法:
var myNewObject = new myObject(); myNewObject.whatAmI();
使用構(gòu)造函數(shù)
現(xiàn)在再來(lái)回歸一下之前的函數(shù)構(gòu)造法:
function myObject() { this.iAm = 'an object'; this.whatAmI = function() { console.log('I am ' + this.iAm); }; };
其實(shí)它看起來(lái)就是個(gè)函數(shù),既然是函數(shù),能不能給它傳參數(shù)呢?將代碼再稍作修改:
function myObject(what) { this.iAm = what; this.whatAmI = function(language) { console.log('I am ' + this.iAm + ' of the ' + language + ' language'); }; };
再將對(duì)象實(shí)例化,并傳入?yún)?shù):
var myNewObject = new myObject('an object'); myNewObject.whatAmI('JavaScript');
程序最終輸出 I am an object of the JavaScript language。
兩種創(chuàng)建對(duì)象的方法,我該用哪種?
對(duì)于字面量方法而言,因?yàn)樗恍枰獙?shí)例化,所以如果修改了某對(duì)象的值,那么這個(gè)對(duì)象的值就永久地被修改了,其它任何地方再訪問(wèn),都是修改后的值。而對(duì)于函數(shù)構(gòu)造法而言,修改值的時(shí)候是修改其實(shí)例的值,它可以實(shí)例化 N 個(gè)對(duì)象出來(lái),每個(gè)對(duì)象都可以擁有自己不同的值,而且互不干擾。比較以下幾段代碼。
先看字面量法:
var myObjectLiteral = { myProperty : 'this is a property' }; console.log(myObjectLiteral.myProperty); // log 'this is a property' myObjectLiteral.myProperty = 'this is a new property'; console.log(myObjectLiteral.myProperty); // log 'this is a new property'
即便創(chuàng)建了一個(gè)新的變量指向這個(gè)對(duì)象,結(jié)果還是一樣的:
var myObjectLiteral = { myProperty : 'this is a property' }; console.log(myObjectLiteral.myProperty); // log 'this is a property' var sameObject = myObjectLiteral; myObjectLiteral.myProperty = 'this is a new property'; console.log(sameObject.myProperty); // log 'this is a new property'
再看函數(shù)構(gòu)造法:
// 用函數(shù)構(gòu)造法 var myObjectConstructor = function() { this.myProperty = 'this is a property' }; // 實(shí)例化一個(gè)對(duì)象 var constructorOne = new myObjectConstructor(); // 實(shí)例化第二個(gè)對(duì)象 var constructorTwo = new myObjectConstructor(); // 輸出 console.log(constructorOne.myProperty); // log 'this is a property' // 輸出 console.log(constructorTwo.myProperty); // log 'this is a property' 和預(yù)期一樣,兩個(gè)對(duì)象的屬性值是一樣的。如果修個(gè)其中一個(gè)對(duì)象的值呢? // 用函數(shù)構(gòu)造法 var myObjectConstructor = function() { this.myProperty = 'this is a property'; }; // 實(shí)例化一個(gè)對(duì)象 var constructorOne = new myObjectConstructor(); // 修改對(duì)象的屬性 constructorOne.myProperty = 'this is a new property'; // 實(shí)例化第二個(gè)對(duì)象 var constructorTwo = new myObjectConstructor(); // 輸出 alert(constructorOne.myProperty); // log 'this is a new property' // 輸出 alert(constructorTwo.myProperty); // log 'this is a property'
可以看到,用函數(shù)構(gòu)造法實(shí)例化出來(lái)的不同對(duì)象,相互是獨(dú)立的,可以各自擁有不同的值。所以說(shuō),到底用哪種方法來(lái)創(chuàng)建對(duì)象,需取決于各自實(shí)際情況。
相關(guān)文章
瀏覽器中url存儲(chǔ)的JavaScript實(shí)現(xiàn)
這篇文章主要介紹了瀏覽器中url存儲(chǔ)的JavaScript實(shí)現(xiàn),并且簡(jiǎn)單講述了輸入url地址后提示過(guò)去輸入歷史記錄的原理,需要的朋友可以參考下2015-07-07javascript開(kāi)發(fā)技術(shù)大全-第3章 js數(shù)據(jù)類(lèi)型
字符串類(lèi)型(string) :由unicode字符、數(shù)字、標(biāo)點(diǎn)符號(hào)組成,在javascript中沒(méi)有char字符類(lèi)型 ,即使只表示一個(gè)字符,也必須用到字符串2011-07-07getElementById在任意一款瀏覽器中都可以用嗎的疑問(wèn)回復(fù)
getElementById在任意一款瀏覽器中都可以用嗎的疑問(wèn)回復(fù)...2007-05-05javaScript獲取對(duì)象中非空的屬性實(shí)現(xiàn)方法詳解
這篇文章主要為大家介紹了javaScript獲取對(duì)象中非空的屬性實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07JavaScript語(yǔ)言核心數(shù)據(jù)類(lèi)型和變量使用介紹
和眾多編程語(yǔ)言一樣,JavaScript也有自己語(yǔ)言的核心,了解并學(xué)好JavaScript的語(yǔ)言核心部分是JavaScript學(xué)習(xí)道路上非常良好的開(kāi)始2013-08-08詳解JavaScript實(shí)現(xiàn)異步Ajax
本文詳細(xì)講解了JavaScript實(shí)現(xiàn)異步Ajax的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05javascript的構(gòu)造函數(shù), 原型,原型鏈和new你了解多少
這篇文章主要為大家詳細(xì)介紹了javascript的構(gòu)造函數(shù), 原型,原型鏈和new,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02