面向?qū)ο蠡A(chǔ)
"面向?qū)ο蟮腏avaScript"這一說法多少有些冗余,因?yàn)镴avaScript語言本就是完全面向?qū)ο蟮模豢赡苡辛硗獾挠梅。但是,初學(xué)編程者(包括JavaScript編程者)共有的一個(gè)缺點(diǎn)就是,功能性地編寫代碼而不使用任何上下文或分組。要完全理解怎么編寫優(yōu)化的JavaScript代碼,你必須理解JavaScript的對(duì)象是怎樣工作的,它們與其它語言有怎樣的不同,以及怎樣讓它們?yōu)槟闼谩?br> 本章的剩余部分我們將討論用JavaScript編寫面向?qū)ο蟠a的基礎(chǔ),在后面的幾章中,我們將看到以這種方式編寫代碼的實(shí)例。
對(duì)象
對(duì)象是JavaScript的基礎(chǔ)。實(shí)際上JavaScript語言中的一切都是對(duì)象,JavaScript的多數(shù)能力也正起源于此。在其最根本的層面上,對(duì)象作為屬性的集合存在,差不多類似于你在其它語言中看到的哈希的概念。程序2-19展示了創(chuàng)建兩個(gè)帶有一組屬性的對(duì)象的基本示例。
程序2-19. 創(chuàng)建簡(jiǎn)單對(duì)象并設(shè)置其屬性的兩個(gè)例子
//創(chuàng)建一個(gè)新對(duì)象并將其存放在obj里 var obj = new Object();
//將該對(duì)象的一些屬性設(shè)置成不同的值 obj.val = 5; obj.click = function(){ alert( "hello" ); };
//下面是等效的代碼,使用了{(lán)...}式縮寫, //和定義對(duì)象屬性的"名稱-值"對(duì) var obj = {
//用名稱-值對(duì)設(shè)置對(duì)象屬性 val: 5, click: function(){ alert( "hello" ); } };
實(shí)際上對(duì)象就這么回事了。然而,事情變得麻煩的地方,在于新對(duì)象(尤其是那些繼承其它對(duì)象屬性的對(duì)象)的創(chuàng)建。
對(duì)象創(chuàng)建
不像大多數(shù)其它面向?qū)ο蟮恼Z言,JavaScript實(shí)際上并沒有類的概念。在大多數(shù)其它的面向?qū)ο笳Z言中,你可以初始化一個(gè)特定的類的實(shí)例,但是在JavaScript中的情況這是這樣。在JavaScript中,對(duì)象能夠創(chuàng)建新的對(duì)象,對(duì)象可以從繼承自其它對(duì)象。整個(gè)概念被稱為"prototypal inheritance"(原型標(biāo)本繼承),將在"公有方法"一節(jié)中有更多論述。 然而,重要的是,不論JavaScript采用哪種對(duì)象方案,總歸要有一個(gè)方式來創(chuàng)建新的對(duì)象。JavaScript的做法是,任何一個(gè)函數(shù)也都能作為一個(gè)對(duì)象被實(shí)例化。實(shí)際上,事情聽起來遠(yuǎn)比它本身更令人困惑。好比有一塊生面團(tuán)(相當(dāng)于原始的對(duì)象),用小甜餅切割器(相當(dāng)于對(duì)象構(gòu)造器,使用對(duì)象的原型prototype)為其成形。 讓我們看看程序2-20中這一機(jī)制的工作的實(shí)例
程序2-20. 創(chuàng)建并使用一個(gè)簡(jiǎn)單的對(duì)象
//一個(gè)簡(jiǎn)單的函數(shù),接受一個(gè)參數(shù)name, //并將其保存于當(dāng)前上下文中 function User( name ) { this.name = name; }
//用指定的name創(chuàng)建上述函數(shù)的新實(shí)例 var me = new User( "My Name" );
//我們可以看到name已經(jīng)被成為對(duì)象本身的屬性 alert( me.name == "My Name" );
//而且它確實(shí)是User對(duì)象的一個(gè)新實(shí)例 alert( me.constructor == User );
//那么,既然User()只是一個(gè)函數(shù), //當(dāng)我們這么處理它的時(shí)候,發(fā)生了什么? User( "Test" );
//因?yàn)閠his上下文沒有被設(shè)置,它缺省地指向全局的window對(duì)象, //這意味著window.name將等于我們提供給它的那個(gè)name alert( window.name == "Test" );
程序2-20說明了constructor屬性的使用。這個(gè)存在于每一個(gè)對(duì)象中的屬性將總是指向創(chuàng)建該對(duì)象的那個(gè)函數(shù)。于是,你可以方便的復(fù)制該對(duì)象,創(chuàng)建一個(gè)新的有共同基類和不同屬性的對(duì)象。示例見程序2-21.
程序2-21. 使用constructor屬性一例
//創(chuàng)建一個(gè)新的、簡(jiǎn)單的User對(duì)象(函數(shù)) function User() {}
//創(chuàng)建一個(gè)新的User對(duì)象 var me = new User();
//也是創(chuàng)建一個(gè)新的User對(duì)象(使用上前一個(gè)對(duì)象的constructor) var you = new me.constructor();
//我們可以看到,實(shí)際上它們的constructor是同一個(gè) alert( me.constructor == you.constructor );
公有方法
公有方法可以完全地被對(duì)象的上下文中的最終使用者訪問。為了實(shí)現(xiàn)這些對(duì)于特定對(duì)象的所有實(shí)例都可用的公共方法,你需要學(xué)習(xí)一個(gè)名為"prototype"的屬性。prototype簡(jiǎn)單地包含一個(gè)對(duì)象,為一個(gè)父對(duì)象的所有新副本充當(dāng)對(duì)基類的引用。本質(zhì)上,prototype的任何屬性對(duì)該對(duì)象的所每一個(gè)實(shí)例都是可用的。創(chuàng)建/引用的過程給了我們一個(gè)廉價(jià)版的繼承,這一點(diǎn)我將在第三章論及。 由于對(duì)象的prototype也是一個(gè)對(duì)象,就跟其它任何對(duì)象一樣,你可以給它附加新的屬性。附加給prototype的新的屬性將成為從原來的prototype對(duì)象實(shí)例化的每個(gè)對(duì)象的一部分,有效地使得該屬性成為公有的(且可為全部實(shí)例所訪問)。程序2-22展示一個(gè)此類例子: 程序2-22. 帶有通過prototype附加的方法的對(duì)象的例子
//創(chuàng)建一個(gè)新的User的構(gòu)造器 function User( name, age ){ this.name = name; this.age = age; }
//為prototype對(duì)象添加一個(gè)新方法 User.prototype.getName = function(){ return this.name; };
//為prototype對(duì)象添加另一個(gè)方法 //注意此方法的上下文將是被實(shí)例化的對(duì)象 User.prototype.getAge = function(){ return this.age; };
//實(shí)例化一個(gè)新的User對(duì)象 var user = new User( "Bob", 44 );
//我們可以看到兩個(gè)方法被附加到了對(duì)象上,有著正確的上下文 alert( user.getName() == "Bob" ); alert( user.getAge() == 44 );
私有方法
私有方法和變量只能被其它的私有方法、私有變量的特權(quán)方法(下一節(jié)將會(huì)論述)訪問。這是一種定義只能在內(nèi)象內(nèi)部訪問的代碼的方式。這一技術(shù)得益于Douglas Crockford的工作。他的網(wǎng)站提供了大量的詳述面向?qū)ο蟮腏avaScript的工作機(jī)制和使用方法的文檔: JavaScript文章列表:http://javascript.crockford.com/ 文章"JavaScript中的私有成員:http://javascript.crockford.com/private.html
我們來看一個(gè)私有方法可以怎樣應(yīng)用中的例子,如程序2-23所示.
程序2-23. 私有方法只能被構(gòu)造函數(shù)使用的示例:
//一個(gè)表示教室的對(duì)象構(gòu)造器 function Classroom( students, teacher ) { //用來顯示教室中的所有學(xué)生的私有方法 function disp() { alert( this.names.join(", ") ); } //課程的數(shù)據(jù)存儲(chǔ)在公有的對(duì)象屬性里 this.students = students; this.teacher = teacher; //調(diào)用私有方法顯示錯(cuò)誤 disp(); }
//創(chuàng)建一新的教室對(duì)象 var class = new Classroom( [ "John", "Bob" ], "Mr. Smith" );
//失敗,因?yàn)閐isp不是該對(duì)象的公有方法 class.disp();
盡管很簡(jiǎn)單,私有方法卻是非常重要的,它可以在保持你的代碼免于沖突同時(shí)允許對(duì)你的用戶可見和可用的施以更強(qiáng)大的控制。接下來,我們來研究特權(quán)方法。它是你的對(duì)象中可以使用的私有方法和共有方法的聯(lián)合。
特權(quán)方法
"特權(quán)方法"一語是Douglas Crockford創(chuàng)造的,用來稱呼那種能夠觀察和維護(hù)私有變量而又可以作為一種公有方法被用戶訪問的方法。程序2-24展示了使用特權(quán)方法的一個(gè)例子。 程序2-24 使用特權(quán)方法一例
//創(chuàng)建一個(gè)新的User對(duì)象構(gòu)造器 function User( name, age ) { //計(jì)算用戶的出生年份 var year = (new Date()).getFullYear() – age;
//創(chuàng)建一個(gè)新特權(quán)方法,對(duì)變量year有訪問權(quán), //但又是公共可訪問的 this.getYearBorn = function(){ return year; }; }
//創(chuàng)建一個(gè)User對(duì)象的新實(shí)例 var user = new User( "Bob", 44 );
//驗(yàn)證返回的出生年份是否正確 alert( user.getYearBorn() == 1962 );
//并注意我們不能訪問對(duì)象的私有屬性year alert( user.year == null );
本質(zhì)上,特權(quán)方法是動(dòng)態(tài)生成的方法,因?yàn)樗鼈兪窃谶\(yùn)行時(shí)而不是代碼初次編譯時(shí)添加給對(duì)象的。這種技術(shù)在計(jì)算量上要比綁定一個(gè)簡(jiǎn)單的方法到對(duì)象的prototype上來得昂貴,但同時(shí)也的強(qiáng)大和靈活得多。程序2-25展示了使用動(dòng)態(tài)生成的方法可以實(shí)現(xiàn)什么。
程序2-25. 新對(duì)象初始化時(shí)創(chuàng)建的動(dòng)態(tài)方法的示例
//創(chuàng)建一個(gè)新的接受properties對(duì)象的對(duì)象 function User( properties ) { //遍歷對(duì)象屬性,確保它作用域正確(如前所述) for ( var i in properties ) { (function(){ //為屬性創(chuàng)建獲取器 this[ "get" + i ] = function() { return properties[i]; };
//為屬性創(chuàng)建設(shè)置器 this[ "set" + i ] = function(val) { properties[i] = val; }; })(); } }
//創(chuàng)建一個(gè)新user對(duì)象實(shí)例,傳入一個(gè)包含屬性的對(duì)象作為種子 var user = new User({ name: "Bob", age: 44 });
//請(qǐng)注意name屬性并不存在,因?yàn)樗趐roperties對(duì)象中,是私有的 alert( user.name == null );
//然而,我們能夠使用用動(dòng)態(tài)生成的方法getname來訪問它 alert( user.getname() == "Bob" );
//最后,我們能看到,通過新生成的動(dòng)態(tài)方法設(shè)置和獲取age都是可以的 user.setage( 22 ); alert( user.getage() == 22 );
(譯注:這段程序是錯(cuò)誤的。那個(gè)匿名函數(shù)里的this錯(cuò)誤地指向了匿名函數(shù)的上下文,而其中的變量i卻又恰仍屬User 的上下文) 動(dòng)態(tài)生成的代碼的力量不可低估。能夠基于變量的值實(shí)時(shí)的生成代碼是極其有用;這與在其它語言(如Lisp)中宏那樣強(qiáng)大的道理是一樣的,不過是放在一種現(xiàn)代編程語言的背景里。接下來,我們將看到一類純粹因其組織上的優(yōu)勢(shì)而有用的方法。
靜態(tài)方法
靜態(tài)方法背后的前提其實(shí)跟其它任何方法是一樣的。然而,最主要的不同在于,這些方法作為對(duì)象的靜態(tài)屬性而存在。作為屬性,它們?cè)谠搶?duì)象的實(shí)例上下文中不可訪問;它們只有在與主對(duì)象本身相同的上下文是可用的。這些與傳統(tǒng)的類繼承的相似點(diǎn),使得他們有點(diǎn)像是靜態(tài)的類方法。 實(shí)際上,以這種方式編寫代碼的唯一好處在于,這種方法保持對(duì)象名稱空間的干凈,——這一概念我就在第三章中更一步論述。程序2-26展示了附加在對(duì)象上的靜態(tài)方法的一個(gè)例子。
程序2-26. 靜態(tài)方法的簡(jiǎn)單示例
//附加在User對(duì)象上的一個(gè)靜態(tài)方法 User.cloneUser = function( user ) { //創(chuàng)建并返回一個(gè)新的User對(duì)象 return new User( //該對(duì)象是其它user對(duì)象的克隆 user.getName(), user.getAge() ); };
靜態(tài)方法是我們遇到的第一種純粹以組織代碼為目的的方法。這是向我們將要看到的下一章的重要過渡。開發(fā)專業(yè)品質(zhì)JavaScript的一個(gè)基本側(cè)觀點(diǎn),就是要有能力快速、平靜地與其它代碼段接口,同時(shí)保持可理解地可用性。這是一個(gè)重要的奮斗目標(biāo),也是我們下一章里所期望達(dá)到的。
經(jīng)典論壇討論: http://bbs.blueidea.com/thread-2734050-1-1.html
本文鏈接:http://www.95time.cn/tech/web/2007/4618.asp
出處:藍(lán)色理想
責(zé)任編輯:moby
上一頁 語言特性:上下文 下一頁
◎進(jìn)入論壇網(wǎng)頁制作、網(wǎng)站綜合版塊參加討論
|