自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用面向?qū)ο蠹夹g(shù)創(chuàng)建高級(jí)Web應(yīng)用程序

開發(fā) 前端
面向?qū)ο蟮木幊?OOP)這種方法廣泛用于多種JavaScript庫(kù),采用這種方法可使代碼庫(kù)更加易于管理和維護(hù)。JavaScript支持OOP,但它的支持方式同流行的Microsoft® .NET框架下的C++、C#、Visual Basic®等語(yǔ)言完全不同,所以,大量使用這些語(yǔ)言的開發(fā)者起初可能會(huì)發(fā)現(xiàn),JavaScript中的OOP比較怪異,同直覺(jué)不符。

最近,我面試了一位具有5年Web應(yīng)用開發(fā)經(jīng)驗(yàn)的軟件開發(fā)人員。她有4年半的JavaScript編程經(jīng)驗(yàn),自認(rèn)為自己具有非常優(yōu)秀的JavaScript技能,可是,隨后我很快發(fā)現(xiàn),實(shí)際上她對(duì)JavaScript卻知之甚少。然而,我并不是要為此而責(zé)怪她。JavaScript就是這么不可思議。有很多人(也包括我自己,這種情況直到最近才有所改觀)都自以為是,覺(jué)得因?yàn)樗麄兌瓹/C++/C#或者具有編程經(jīng)驗(yàn),便以為他們非常擅長(zhǎng)JavaScript這門語(yǔ)言。

從某個(gè)角度講,這種自以為是也并非毫無(wú)道理。用JavaScript做一些簡(jiǎn)單的事情是非常容易的。其入門的門檻非常低;這個(gè)語(yǔ)言待人寬厚,并不苛求你必須懂它很多才能開始用它編寫代碼。甚至對(duì)于非程序員來(lái)說(shuō),也可以僅花個(gè)把小時(shí)就能夠上手用它為他的網(wǎng)站編寫幾段或多或少都有些用的腳本。

實(shí)際上直到最近,無(wú)論懂的JavaScript有多么少,僅僅在MSDN® DHTML參考資料以及我在C++/C#方面編程經(jīng)驗(yàn)的幫助下,我都能夠湊合過(guò)下去。直到我在工作中真正開始編寫AJAX應(yīng)用時(shí),我才發(fā)現(xiàn)我對(duì) JavaScript的了解有多么欠缺。這種新一代的Web應(yīng)用復(fù)雜的交互特性要求使用一種完全不同的方式來(lái)編寫JavaScript代碼。這些都是非常嚴(yán)肅的JavaScript應(yīng)用!我們以往那種漫不經(jīng)心編寫腳本的方法不靈了。

面向?qū)ο蟮木幊?OOP)這種方法廣泛用于多種JavaScript庫(kù),采用這種方法可使代碼庫(kù)更加易于管理和維護(hù)。JavaScript支持OOP,但它的支持方式同流行的Microsoft® .NET框架下的C++、C#、Visual Basic®等語(yǔ)言完全不同,所以,大量使用這些語(yǔ)言的開發(fā)者起初可能會(huì)發(fā)現(xiàn),JavaScript中的OOP比較怪異,同直覺(jué)不符。我寫這篇文章就是要對(duì)JavaScript到底是如何支持面向?qū)ο缶幊痰囊约叭绾胃咝Ю眠@種支進(jìn)行面向?qū)ο蟮腏avaScript開發(fā)進(jìn)行深入討論。接下來(lái)讓我們開始談?wù)剬?duì)象(除了對(duì)象還能有別的嗎?)吧。

JavaScript對(duì)象是字典

在C++或C#中,當(dāng)談及對(duì)象時(shí),我們指的是類或者結(jié)構(gòu)的實(shí)例。對(duì)象根據(jù)實(shí)例化出它的模版(也即,類)的不同而具有不同的屬性和方法。 JavaScript對(duì)象不是這樣的。在JavaScript中,對(duì)象僅僅是name/value對(duì)的集合,我們可以把JavaScript對(duì)象看作字典,字典中的鍵為字符串。我們可以用我們熟悉的"." (點(diǎn))操作符或者一般用于字典的"[]"操作符,來(lái)獲取或者設(shè)置對(duì)象的屬性。下面的代碼片段

  1. var userObject = new Object();  
  2. userObject.lastLoginTime = new Date();  
  3. alert(userObject.lastLoginTime); 

同這段代碼所做的完全是同樣的事情:

  1. var userObject = {}; // equivalent to new Object()  
  2. userObject["lastLoginTime"] = new Date();  
  3. alert(userObject["lastLoginTime"]); 

我們還可以用這樣的方式,直接在userObject的定義中定義lastLoginTime屬性:

  1. var userObject = { "lastLoginTime"new Date() };  
  2. alert(userObject.lastLoginTime); 

請(qǐng)注意這同C# 3.0的對(duì)象初始化表達(dá)式是多么的相似。另外,熟悉Python的讀者會(huì)發(fā)現(xiàn),在第二段和第三段代碼中,我們實(shí)例化userObject的方式就是 Python中指定字典的方式。這里唯一的區(qū)別的就是,JavaScript中的對(duì)象/字典只接受字符串作為鍵,而Python中字典則無(wú)此限制。

這些例子也表明,同C++或者C#對(duì)象相比,JavaScript對(duì)象是多么地更加具有可塑性。屬性lastLoginTime不必事先聲明,如果在使用這個(gè)屬性的時(shí)候userObject還不具有以此為名的屬性,就會(huì)在userObject中把這個(gè)屬性添加進(jìn)來(lái)。如果記住了JavaScript對(duì)象就是字典的話,你就不會(huì)對(duì)此大驚小怪了 —— 畢竟我們隨時(shí)都可以把新鍵(及其對(duì)應(yīng)的值)添加到字典中去。

JavaScript對(duì)象的屬性就是這個(gè)樣子的。那么,JavaScript對(duì)象的方法呢?和屬性一樣,JavaScript仍然和C++/C#不同。為了理解對(duì)象的方法,就需要首先仔細(xì)看看JavaScript函數(shù)。

JavaScript中的函數(shù)具有首要地位

在許多編程語(yǔ)言中,函數(shù)和對(duì)象一般都認(rèn)為是兩種不同的東西??稍贘avaScript中,它們之間的區(qū)別就沒(méi)有那么明顯了 —— JavaScript中的函數(shù)實(shí)際上就是對(duì)象,只不過(guò)這個(gè)對(duì)象具有同其相關(guān)聯(lián)的一段可執(zhí)行代碼。請(qǐng)看下面這段再普通不過(guò)的代碼:

  1. function func(x) {  
  2.     alert(x);  
  3. }  
  4. func("blah"); 

這是JavaScript中定義函數(shù)最常用的方式了。但是,你還可以先創(chuàng)建一個(gè)匿名函數(shù)對(duì)象再將該對(duì)象賦值給變量func,也即,象下面那樣,定義出完全相同的函數(shù)

  1. var func = function(x) {  
  2.     alert(x);  
  3. };  
  4. func("blah2"); 

或者甚至通過(guò)使用Function構(gòu)造器,向下面這樣來(lái)定義它:

  1. var func = new Function("x""alert(x);");  
  2. func("blah3"); 

這表明,函數(shù)實(shí)際上就是一個(gè)支持函數(shù)調(diào)用操作的對(duì)象。***這種使用Function構(gòu)造器來(lái)定義函數(shù)的方式并不常用,但卻為我們帶來(lái)很多很有趣的可能,其原因可能你也已經(jīng)發(fā)現(xiàn)了,在這種函數(shù)定義的方式中,函數(shù)體只是Function構(gòu)造器的一個(gè)字符串型的參數(shù)。這就意味著,你可以在JavaScript運(yùn)行的時(shí)候構(gòu)造出任意的函數(shù)。

要進(jìn)一步證明函數(shù)是對(duì)象,你可以就象為任何其它JavaScript對(duì)象一樣,為函數(shù)設(shè)置或添加屬性:

  1. function sayHi(x) {  
  2.     alert("Hi, " + x + "!");  
  3. }  
  4.  
  5. sayHi.text = "Hello World!";  
  6. sayHi["text2"] = "Hello World... again.";  
  7.  
  8. alert(sayHi["text"]); // displays "Hello World!"  
  9. alert(sayHi.text2); // displays "Hello World... again." 

作為對(duì)象,函數(shù)還可以賦值給變量、作為參數(shù)傳遞給其它函數(shù)、作為其它函數(shù)的返回值、保存為對(duì)象的屬性或數(shù)組中的一員等等。圖1所示為其中一例。

圖1 函數(shù)在JavaScript具有首要地位

  1. // assign an anonymous function to a variable  
  2. var greet = function(x) {  
  3.     alert("Hello, " + x);  
  4. };  
  5.  
  6. greet("MSDN readers");  
  7.  
  8. // passing a function as an argument to another  
  9. function square(x) {  
  10.     return x * x;  
  11. }  
  12.  
  13. function operateOn(num, func) {  
  14.     return func(num);  
  15. }  
  16.  
  17. // displays 256  
  18. alert(operateOn(16, square));  
  19.  
  20. // functions as return values  
  21. function makeIncrementer() {  
  22.     return function(x) { return x + 1; };  
  23. }  
  24.  
  25. var inc = makeIncrementer();  
  26. // displays 8  
  27. alert(inc(7));  
  28.  
  29. // functions stored as array elements  
  30. var arr = [];  
  31. arr[0] = function(x) { return x * x; };  
  32. arr[1] = arr[0](2);  
  33. arr[2] = arr[0](arr[1]);  
  34. arr[3] = arr[0](arr[2]);  
  35.  
  36. // displays 256  
  37. alert(arr[3]);  
  38.  
  39. // functions as object properties  
  40. var obj = { "toString" : function() { return "This is an object."; } };  
  41.  
  42. // calls obj.toString()  
  43. alert(obj); 

記住這一點(diǎn)后,為對(duì)象添加方法就簡(jiǎn)單了,只要選擇一個(gè)函數(shù)名并把一個(gè)函數(shù)賦值為這個(gè)函數(shù)名即可。接下來(lái)我通過(guò)將三個(gè)匿名函數(shù)分別賦值給各自相應(yīng)的方法名,為一個(gè)對(duì)象定義了三個(gè)方法:

  1. var myDog = {  
  2.     "name" : "Spot",  
  3.     "bark" : function() { alert("Woof!"); },  
  4.     "displayFullName" : function() {  
  5.         alert(this.name + " The Alpha Dog");  
  6.     },  
  7.  
  8.     "chaseMrPostman" : function() {   
  9.         // implementation beyond the scope of this article   
  10.     }      
  11. };  
  12.  
  13. myDog.displayFullName();   
  14. myDog.bark(); // Woof! 

函數(shù)displayFullName中"this"關(guān)鍵字的用法對(duì)C++/C#開發(fā)者來(lái)說(shuō)并不陌生 —— 該方法是通過(guò)哪個(gè)對(duì)象調(diào)用的,它指的就是哪個(gè)對(duì)象(使用Visual Basic的開發(fā)者也應(yīng)該熟悉這種用法 —— 只不過(guò)"this"在Visual Basic稱作"Me")。因此在上面的例子中,displayFullName中"this"的值指的就是myDog對(duì)象。但是,"this"的值不是靜態(tài)的。如果通過(guò)別的對(duì)象對(duì)函數(shù)進(jìn)行調(diào)用,"this"的值也會(huì)隨之指向這個(gè)別的對(duì)象,如圖2所示。

#p#

圖2 “this”隨著對(duì)象的改變而改變

  1. function displayQuote() {  
  2.     // the value of "this" will change; depends on   
  3.     // which object it is called through  
  4.     alert(this.memorableQuote);      
  5. }  
  6.  
  7. var williamShakespeare = {  
  8.     "memorableQuote""It is a wise father that knows his own child.",   
  9.     "sayIt" : displayQuote  
  10. };  
  11.  
  12. var markTwain = {  
  13.     "memorableQuote""Golf is a good walk spoiled.",   
  14.     "sayIt" : displayQuote  
  15. };  
  16.  
  17. var oscarWilde = {  
  18.     "memorableQuote""True friends stab you in the front."   
  19.     // we can call the function displayQuote  
  20.     // as a method of oscarWilde without assigning it   
  21.     // as oscarWilde’s method.   
  22.     //"sayIt" : displayQuote  
  23. };  
  24.  
  25. williamShakespeare.sayIt(); // true, true  
  26. markTwain.sayIt(); // he didn’t know where to play golf  
  27.  
  28. // watch this, each function has a method call()  
  29. // that allows the function to be called as a   
  30. // method of the object passed to call() as an  
  31. // argument.   
  32. // this line below is equivalent to assigning  
  33. // displayQuote to sayIt, and calling oscarWilde.sayIt().  
  34.  
  35. displayQuote.call(oscarWilde); // ouch! 

圖2***一行的代碼是將函數(shù)作為一個(gè)對(duì)象的方法進(jìn)行調(diào)用的另外一種方式。別忘了,JavaScript中的函數(shù)是對(duì)象。每個(gè)函數(shù)對(duì)象都有一個(gè)叫做call的方法,這個(gè)方法會(huì)將函數(shù)作為該方法的***個(gè)參數(shù)的方法進(jìn)行調(diào)用。也就是說(shuō),無(wú)論將哪個(gè)對(duì)象作為***個(gè)參數(shù)傳遞給call方法,它都會(huì)成為此次函數(shù)調(diào)用中"this"的值。后面我們就會(huì)看到,這個(gè)技術(shù)在調(diào)用基類構(gòu)造器時(shí)會(huì)非常有用。

有一點(diǎn)要記住,那就是永遠(yuǎn)不要調(diào)用不屬于任意對(duì)象卻包含有"this"的函數(shù)。如果調(diào)用了的話,就會(huì)攪亂全局命名空間。這是因?yàn)樵谶@種調(diào)用中,"this"將指向Global對(duì)象,此舉將嚴(yán)重?fù)p害你的應(yīng)用。例如,下面的腳本將會(huì)改變JavaScript的全局函數(shù)isNaN的行為。我們不推薦這么干。

  1. alert("NaN is NaN: " + isNaN(NaN));  
  2.  
  3. function x() {  
  4.     this.isNaN = function() {   
  5.         return "not anymore!";  
  6.     };  
  7. }  
  8.  
  9. // alert!!! trampling the Global object!!!  
  10. x();  
  11.  
  12. alert("NaN is NaN: " + isNaN(NaN)); 

到此我們已經(jīng)看過(guò)了創(chuàng)建對(duì)象并為其添加熟悉和方法的幾種方式。但是,如果你仔細(xì)看了以上所舉的所以代碼片段就會(huì)發(fā)現(xiàn),所有的熟悉和方法都是在對(duì)象的定義之中通過(guò)硬性編碼定義的。要是你需要對(duì)對(duì)象的創(chuàng)建進(jìn)行更加嚴(yán)格的控制,那該怎么辦?例如,你可能會(huì)需要根據(jù)某些參數(shù)對(duì)對(duì)象屬性中的值進(jìn)行計(jì)算,或者你可能需要將對(duì)象的屬性初始化為只有到代碼運(yùn)行時(shí)才會(huì)得到的值,你還有可能需要?jiǎng)?chuàng)建一個(gè)對(duì)象的多個(gè)實(shí)例,這些要求也是非常常見的。

在C#中,我們使用類類實(shí)例化出對(duì)象實(shí)例。但是JavaScript不一樣,它并沒(méi)有類的概念。相反, 在下一小節(jié)你將看到,你可以利用這一點(diǎn):將函數(shù)同"new"操作符一起使用就可以把函數(shù)當(dāng)著構(gòu)造器來(lái)用。

有構(gòu)造函數(shù)但沒(méi)有類

JavaScript中的OOP最奇怪的事,如前所述,就是JavaScript沒(méi)有C#和C++ 中所具有的類。在C#中,通過(guò)如下這樣的代碼

  1. Dog spot = new Dog(); 

能夠得到一個(gè)對(duì)象,這個(gè)對(duì)象就是Dog類的一個(gè)實(shí)例。但在JavaScript中根本就沒(méi)有類。要想得到同類最近似的效果,可以象下面這樣定義一個(gè)構(gòu)造器函數(shù):

  1. function DogConstructor(name) {  
  2.     this.name = name;  
  3.     this.respondTo = function(name) {  
  4.         if(this.name == name) {  
  5.             alert("Woof");          
  6.         }  
  7.     };  
  8. }  
  9.  
  10. var spot = new DogConstructor("Spot");  
  11. spot.respondTo("Rover"); // nope  
  12. spot.respondTo("Spot"); // yeah! 

好吧,這里都發(fā)生了什么?先請(qǐng)不要管DogConstructor 函數(shù)的定義,仔細(xì)看看這行代碼:

  1. var spot = new DogConstructor("Spot"); 

"new"操作符所做的事情很簡(jiǎn)單。首先,它會(huì)創(chuàng)建出一個(gè)新的空對(duì)象。然后,緊跟其后的函數(shù)調(diào)用就會(huì)得到執(zhí)行,并且會(huì)將那個(gè)新建的空對(duì)象設(shè)置為該函數(shù)中"this"的值。換句話說(shuō),這行帶有"new"操作符的代碼可以看作等價(jià)于下面這兩行代碼:

  1. // create an empty object  
  2. var spot = {};   
  3. // call the function as a method of the empty object  
  4. DogConstructor.call(spot, "Spot"); 

在DogConstructor的函數(shù)體中可以看出,調(diào)用該函數(shù)就會(huì)對(duì)調(diào)用中關(guān)鍵字"this"所指的對(duì)象進(jìn)行初始化。采用這種方式,你就可以為對(duì)象創(chuàng)建模版了!無(wú)論何時(shí)當(dāng)你需要?jiǎng)?chuàng)建類似的對(duì)象時(shí),你就可以用"new"來(lái)調(diào)用該構(gòu)造器函數(shù),然后你就能夠得到一個(gè)完全初始化好的對(duì)象。這和類看上去非常相似,不是嗎?實(shí)際上,JavaScript中構(gòu)造器函數(shù)的名字往往就是你想模擬的類的名字,所以上面例子中的構(gòu)造函數(shù)你就可以直接命名為Dog:

  1. // Think of this as class Dog  
  2. function Dog(name) {  
  3.     // instance variable   
  4.     this.name = name;  
  5.  
  6.     // instance method? Hmmm...  
  7.     this.respondTo = function(name) {  
  8.         if(this.name == name) {  
  9.             alert("Woof");          
  10.         }  
  11.     };  
  12. }  
  13.  
  14. var spot = new Dog("Spot"); 

上面在Dog的定義中,我定義了一個(gè)叫做name的實(shí)例變量。將Dog作為構(gòu)造器函數(shù)使用而創(chuàng)建的每個(gè)對(duì)象都有自己的一份叫做name的實(shí)例變量(如前所述,name就是該對(duì)象的字典入口)。這符合我們的期望;畢竟每個(gè)對(duì)象都需屬于自己的一份實(shí)例變量,只有這樣才能保存它自己的狀態(tài)。但是如果你再看接下來(lái)的那行代碼,就會(huì)發(fā)現(xiàn)Dog的每個(gè)實(shí)例都有自己的一份respondTo方法,這可是個(gè)浪費(fèi);respondTo的實(shí)例你只需要一個(gè),只有將這一個(gè)實(shí)例在所有的Dog實(shí)例間共享即可!你可以把respondTo的定義從Dog中拿出來(lái),這樣就可以克服此問(wèn)題了,就向下面這樣:

  1. function respondTo() {  
  2.     // respondTo definition  
  3. }  
  4.  
  5. function Dog(name) {  
  6.     this.name = name;  
  7.     // attached this function as a method of the object  
  8.     this.respondTo = respondTo;  

這樣一來(lái),Dog的所有實(shí)例(也即,用構(gòu)造器函數(shù)Dog創(chuàng)建的所有實(shí)例)都可以共享respondTo方法的同一個(gè)實(shí)例了。但是,隨著方法數(shù)量的增加,這種方式維護(hù)起來(lái)會(huì)越來(lái)越困難。***你的代碼庫(kù)中會(huì)堆積大量的全局函數(shù),而且,隨著“類”的數(shù)量不斷增加,特別是這些類的方法具有類似的方法名時(shí),情況會(huì)變得更加糟糕。這里還有一個(gè)更好的辦法,就是使用原型對(duì)象,這就是下一個(gè)小節(jié)要討論的內(nèi)容。

原型(Prototype)

原型對(duì)象是JavaScript面向?qū)ο缶幊讨械囊粋€(gè)核心概念。原型這個(gè)名稱來(lái)自于這樣一個(gè)概念:在JavaScript中,所有對(duì)象都是通過(guò)對(duì)已有的樣本(也即,原型)對(duì)象進(jìn)行拷貝而創(chuàng)建的。該原型對(duì)象的所有屬性和方法都會(huì)成為通過(guò)使用該原型的構(gòu)造函數(shù)生成的對(duì)象的屬性和方法。你可以認(rèn)為,這些對(duì)象從它們的原型中繼承了相應(yīng)的屬性和方法。當(dāng)你象這樣來(lái)創(chuàng)建一個(gè)新的Dog對(duì)象時(shí)

  1. var buddy = new Dog("Buddy"); 

buddy所引用的對(duì)象將從它的原型中繼承到相應(yīng)的屬性和方法,雖然僅從上面這一行代碼可能會(huì)很難看出來(lái)其原型來(lái)自哪里。buddy對(duì)象的原型來(lái)自來(lái)自構(gòu)造器函數(shù)(在此例中指的就是函數(shù)Dog)的一個(gè)屬性。

在JavaScript中,每個(gè)函數(shù)都有一個(gè)叫做“prototype”的屬性,該屬性指向一個(gè)原型對(duì)象。發(fā)過(guò)來(lái),該原型對(duì)象據(jù)有一個(gè)叫做"constructor"的屬性,該屬性又指回了這個(gè)函數(shù)本身。這是一種循環(huán)引用;圖3 更好地揭示出了這種環(huán)形關(guān)系。


圖3 每個(gè)函數(shù)的原型都具有一個(gè)叫做Constructor的屬性 

好了,當(dāng)一個(gè)函數(shù)(比如上例中的Dog)和"new"操作符一起使用,創(chuàng)建出一個(gè)對(duì)象時(shí),該對(duì)象將從Dog.prototype中繼承所有的屬性。在圖3中,你可以看出,Dog.prototype對(duì)象具有一個(gè)指會(huì)Dog函數(shù)的construtor屬性,每個(gè)Dog對(duì)象(它們繼承自Dog.prototype)將同樣也具有一個(gè)指會(huì)Dog函數(shù)的constructor屬性。圖4中的代碼證明了這一點(diǎn)。構(gòu)造器函數(shù)、原型對(duì)象以及用它們創(chuàng)建出來(lái)的對(duì)象這三者之間的關(guān)系如圖5所示。

#p#

圖4 對(duì)象同樣也具有它們?cè)偷膶傩?/strong>

  1. var spot = new Dog("Spot");  
  2.  
  3. // Dog.prototype is the prototype of spot  
  4. alert(Dog.prototype.isPrototypeOf(spot));  
  5.  
  6. // spot inherits the constructor property  
  7. // from Dog.prototype  
  8. alert(spot.constructor == Dog.prototype.constructor);  
  9. alert(spot.constructor == Dog);  
  10.  
  11. // But constructor property doesn’t belong  
  12. // to spot. The line below displays "false"  
  13. alert(spot.hasOwnProperty("constructor"));  
  14.  
  15. // The constructor property belongs to Dog.prototype  
  16. // The line below displays "true"  
  17. alert(Dog.prototype.hasOwnProperty("constructor")); 


圖5 繼承自它們的原型的實(shí)例

有些讀者可能已經(jīng)注意到了圖4中對(duì)hasOwnProperty方法和isPrototypeOf方法的調(diào)用。這些方法又來(lái)自哪里呢?它們并不是來(lái)自Dog.prototype。實(shí)際上,JavaScript中還有其它一些類似于toString、 toLocaleString和valueOf等等我們可以直接對(duì)Dog.prototype以及Dog的實(shí)例進(jìn)行調(diào)用的方法,但它們統(tǒng)統(tǒng)都不是來(lái)自于 Dog.prototype的。其實(shí)就象.NET框架具有System.Object一樣,JavaScript中也有 Object.prototype,它是所有類的最***的基類。(Object.prototype的原型為null。)

在這個(gè)例子中,請(qǐng)記住Dog.prototype也是一個(gè)對(duì)象。它也是通過(guò)對(duì)Object的構(gòu)造函數(shù)進(jìn)行調(diào)用后生成的,雖然這一點(diǎn)在代碼中并不直接出現(xiàn):

  1. Dog.prototype = new Object(); 

所以,就如同Dog的實(shí)例繼承自Dog.prototype一樣,Dog.prototype繼承自O(shè)bject.prototype。這就使得Dog的所有實(shí)例也都會(huì)繼承Object.prototype的方法和實(shí)例。

每個(gè)JavaScript對(duì)象都會(huì)繼承一個(gè)原型鏈,該鏈的最末端都是Object.prototype。請(qǐng)注意,到此為止你在這里所見到的繼承都是活生生的對(duì)象間的繼承。這同你通常所認(rèn)識(shí)的類在定義時(shí)形成的繼承的概念不同。因此,JavaScript中的繼承要來(lái)得更加的動(dòng)態(tài)化。繼承的算法非常簡(jiǎn)單,就是這樣的:當(dāng)你要訪問(wèn)一個(gè)對(duì)象的屬性/方法時(shí),JavaScript會(huì)首先對(duì)該屬性/方法是否定義于該對(duì)象之中。如果不是,接下來(lái)就要對(duì)該對(duì)象的原型進(jìn)行檢查。如果還沒(méi)有發(fā)現(xiàn)相應(yīng)的定義,然后就會(huì)對(duì)該對(duì)象的原型的原型進(jìn)行檢查,并以此類推,直到碰到Object.prototype。圖6所示即為這個(gè)解析過(guò)程。


圖6 在原型鏈中對(duì)toString()方法進(jìn)行解析

JavaScript這種動(dòng)態(tài)解析屬性訪問(wèn)和方法調(diào)用的方式將對(duì)JavaScript帶來(lái)一些影響。對(duì)原型對(duì)象的修改會(huì)馬上在繼承它的對(duì)象中得以體現(xiàn),即使這種修改是在對(duì)象創(chuàng)建后才進(jìn)行的也無(wú)關(guān)緊要。如果你在對(duì)象中定義了一個(gè)叫做X的屬性/方法,那么該對(duì)象原型中同名的屬性/方法就會(huì)無(wú)法訪問(wèn)到。例如,你可以通過(guò)在Dog.prototype中定義一個(gè)toString方法來(lái)對(duì)Object.prototype中的toString方法進(jìn)行重載。所有修改指揮在一個(gè)方向上產(chǎn)生作用,即慈寧宮原型到繼承它的對(duì)象這個(gè)方向,相反則不然。

圖7所示即為這種影響。圖7還演示了如何解決前文碰到的避免不必要的方法實(shí)例問(wèn)題。不用讓每個(gè)對(duì)象都具有一個(gè)單獨(dú)的方法對(duì)象的實(shí)例,你可以通過(guò)將方法放到其原型之中來(lái)讓所有對(duì)象共享同一個(gè)方法。此例中,getBreed方法由 rover和spot共享 —— 至少直到在spot中重載了getBreed(譯者注:原文為toString,應(yīng)為筆誤)方法之前。spot在重載之后就具有自己版本的getBreed方法,但是rover對(duì)象以及隨后使用new和GreatDane創(chuàng)建的對(duì)象仍將繼承的是定義于GreatDane.prototype對(duì)象的getBreed方法。

圖7  從原型中進(jìn)行繼承

  1. function GreatDane() { }  
  2.  
  3. var rover = new GreatDane();  
  4. var spot = new GreatDane();  
  5.  
  6. GreatDane.prototype.getBreed = function() {  
  7.     return "Great Dane";  
  8. };  
  9.  
  10. // Works, even though at this point  
  11. // rover and spot are already created.  
  12. alert(rover.getBreed());  
  13.  
  14. // this hides getBreed() in GreatDane.prototype  
  15. spot.getBreed = function() {  
  16.     return "Little Great Dane";  
  17. };  
  18.  
  19. alert(spot.getBreed());   
  20.  
  21. // but of course, the change to getBreed   
  22. // doesn’t propagate back to GreatDane.prototype  
  23. // and other objects inheriting from it,  
  24. // it only happens in the spot object  
  25. alert(rover.getBreed()); 

靜態(tài)屬性和方法

有些時(shí)候你會(huì)需要同類而不是實(shí)例捆綁到一起的屬性或方法 —— 也即,靜態(tài)屬性和靜態(tài)方法。在JavaScript中這很容易就能做到,因?yàn)楹瘮?shù)就是對(duì)象,所以可以隨心所欲為其設(shè)置屬性和方法。既然構(gòu)造器函數(shù)在 JavaScript代表了類這個(gè)概念,所以你可以通過(guò)在構(gòu)造器函數(shù)中設(shè)置屬性和昂奮來(lái)為一個(gè)類添加靜態(tài)方法和屬性,就象這樣:

  1. function DateTime() { }  
  2.  
  3.     // set static method now()  
  4.     DateTime.now = function() {  
  5.         return new Date();  
  6.     };  
  7.  
  8.     alert(DateTime.now()); 

在JavaScript調(diào)用靜態(tài)方法的語(yǔ)法實(shí)際上和C#完全相同。既然構(gòu)造器函數(shù)就是類的名字,所以這也不應(yīng)該有什么奇怪的。這樣你就有了類、共有屬性/ 方法以及靜態(tài)屬性/方法。你還需要什么呢?當(dāng)然,還需要私有成員。但是,JavaScript并不直接支持私有成員(這方面它也不支持protected 成員)。對(duì)象的所以屬性和方法所有人都可以訪問(wèn)得到。這里有一種在類中定義出私有成員的方法,但要完成這個(gè)任務(wù)就需要首先對(duì)閉包有所了解。

閉包

我學(xué)JavaScript完全是迫不得已。因?yàn)槲乙庾R(shí)到,不學(xué)習(xí)JavaScript,就無(wú)法為在工作中參加編寫真正的AJAX應(yīng)用做好準(zhǔn)備。起初,我有種在程序員的級(jí)別中下降了不少等級(jí)的感覺(jué)。(我要學(xué)JavaScript了!我那些使用C++的朋友該會(huì)怎么說(shuō)我???)但是一旦我克服了起初的抗拒心理之后,我很快發(fā)現(xiàn),JavaScript實(shí)際上是一門功能強(qiáng)大、表達(dá)能力極強(qiáng)而且很小巧的語(yǔ)言。它甚至擁有一些其它更加流行的語(yǔ)言才剛剛開始支持的特性。

JavaScript中更加高級(jí)的一個(gè)特性便是它對(duì)閉包的支持,在C# 2.0中是通過(guò)匿名方法對(duì)閉包提供支持的。閉包是一種運(yùn)行時(shí)的現(xiàn)象,它產(chǎn)生于內(nèi)部函數(shù)(在C#中成為內(nèi)部匿名方法)本綁定到了其外部函數(shù)的局部變量之上的時(shí)候。顯然,除非內(nèi)部函數(shù)可以通過(guò)某種方式在外部函數(shù)之外也可以讓其可以訪問(wèn)得到,否則這也沒(méi)有多大意義。舉個(gè)例子就可以把這個(gè)現(xiàn)象說(shuō)得更清楚了。

假如你需要基于一個(gè)簡(jiǎn)單評(píng)判標(biāo)準(zhǔn)對(duì)一個(gè)數(shù)字序列進(jìn)行過(guò)濾,該標(biāo)準(zhǔn)就是大于100的數(shù)字可以留下,但要把其它的所以數(shù)字都過(guò)濾掉。你可以編寫寫一個(gè)如圖8所示的函數(shù)。

圖8 基于謂詞(Predicate)對(duì)元素進(jìn)行過(guò)濾

  1. function filter(pred, arr) {  
  2.  
  3.     var len = arr.length;  
  4.     var filtered = []; // shorter version of new Array();  
  5.  
  6.     // iterate through every element in the array...  
  7.     for(var i = 0; i < len; i++) {  
  8.         var val = arr[i];  
  9.         // if the element satisfies the predicate let it through  
  10.         if(pred(val)) {  
  11.             filtered.push(val);  
  12.         }  
  13.     }  
  14.     return filtered;  
  15. }  
  16.  
  17. var someRandomNumbers = [12, 32, 1, 3, 2, 2, 234, 236, 632,7, 8];  
  18. var numbersGreaterThan100 = filter(  
  19.     function(x) { return (x > 100) ? true : false; },   
  20.     someRandomNumbers);  
  21.  
  22.  
  23. // displays 234, 236, 632  
  24. alert(numbersGreaterThan100); 

但是現(xiàn)在你想新建一個(gè)不同的過(guò)濾標(biāo)準(zhǔn),比方說(shuō),這次只有大于300的數(shù)字才能留下。你可以這么做:

  1. var greaterThan300 = filter(  
  2.     function(x) { return (x > 300) ? true : false; },   
  3.     someRandomNumbers); 

可能還需要留下大于50、25、10、600等等的數(shù)字,然而,你是如此聰明,很快就會(huì)發(fā)現(xiàn)它們使用的都是“大于”這同一個(gè)謂詞,所不同的只是其中的數(shù)字。所以,你可以把具體的數(shù)字拿掉,編寫出這么一個(gè)函數(shù):

  1. function makeGreaterThanPredicate(lowerBound) {  
  2.     return function(numberToCheck) {  
  3.         return (numberToCheck > lowerBound) ? true : false;  
  4.     };  

有了這個(gè)函數(shù)你就可以象下面這樣做了:

  1. var greaterThan10 = makeGreaterThanPredicate(10);  
  2. var greaterThan100 = makeGreaterThanPredicate(100);  
  3. alert(filter(greaterThan10, someRandomNumbers));  
  4. alert(filter(greaterThan100, someRandomNumbers)); 

請(qǐng)注意makeGreaterThanPredicate函數(shù)所返回的內(nèi)部匿名函數(shù)。該匿名內(nèi)部函數(shù)使用了lowerBound,它是傳遞給 makeGreaterThanPredicate的一個(gè)參數(shù)。根據(jù)通常的變量范圍規(guī)則,當(dāng)makeGreater­ThanPredicate函數(shù)退出后,lowerBound就離開了它的作用范圍!但是在此種情況下,內(nèi)部匿名函數(shù)仍然還攜帶著它,即使 make­GreaterThanPredicate早就退出了也還是這樣。這就是我們稱之為閉包的東西 ——— 因?yàn)閮?nèi)部函數(shù)關(guān)閉著它的定義所在的環(huán)境(也即,外部函數(shù)的參數(shù)和局部變量)。

乍一看,閉包也許沒(méi)什么大不了的。但是如果使用得當(dāng),使用它可以在將你的點(diǎn)子轉(zhuǎn)變?yōu)榇a時(shí),為你打開很多非常有意思的新思路。在JavaScript中閉包最值得關(guān)注的用途之一就是用它來(lái)模擬出類的私有變量。

#p#

模擬私有屬性

好的,現(xiàn)在讓我們來(lái)看看在閉包的幫助下怎樣才能模擬出私有成員。函數(shù)中的私有變量通常在函數(shù)之外是訪問(wèn)不到的。在函數(shù)執(zhí)行結(jié)束后,實(shí)際上局部變量就會(huì)永遠(yuǎn)消失。然而,如果內(nèi)部函數(shù)捕獲了局部變量的話,這樣的局部變量就會(huì)繼續(xù)存活下去。 這個(gè)實(shí)情就是在JavaScript中模擬出私有屬性的關(guān)鍵所在。請(qǐng)看下面的Person類:

  1. function Person(name, age) {  
  2.     this.getName = function() { return name; };  
  3.     this.setName = function(newName) { name = newName; };  
  4.     this.getAge = function() { return age; };  
  5.     this.setAge = function(newAge) { age = newAge; };  

參數(shù)name和age對(duì)構(gòu)造器函數(shù)Person來(lái)說(shuō)就是局部變量。一旦Person函數(shù)返回之后,name 和age就應(yīng)該被認(rèn)為永遠(yuǎn)消失了。然而,這兩個(gè)參數(shù)被4個(gè)內(nèi)部函數(shù)捕獲,這些內(nèi)部函數(shù)被賦值為Person實(shí)例的方法了,因此這樣一來(lái)就使得name和 age能夠繼續(xù)存活下去,但卻被很嚴(yán)格地限制為只有通過(guò)這4個(gè)方法才能訪問(wèn)到它們。所以,你可以這樣做:

  1. var ray = new Person("Ray", 31);  
  2. alert(ray.getName());  
  3. alert(ray.getAge());  
  4. ray.setName("Younger Ray");  
  5. // Instant rejuvenation!  
  6. ray.setAge(22);  
  7.  
  8. alert(ray.getName() + " is now " + ray.getAge() +   
  9.       " years old."); 

不必在構(gòu)造器中進(jìn)行初始化的私有成員可以聲明為構(gòu)造器函數(shù)的局部變量,就象這樣:

要注意的是,這樣的私有成員同我們所認(rèn)為的C#中的私有成員稍有不同。在C#中,類的公開方法可以直接訪問(wèn)類的私有成員。但是在JavaScript中,私有成員只有通過(guò)在閉包中包含有這些私有成員的方法來(lái)訪問(wèn)(這樣的方法通常稱為特權(quán)方法,因?yàn)樗鼈儾煌谄胀ǖ墓_方法)。因此,在Person的公開方法中,你依然可以通過(guò)Person的特權(quán)方法方法來(lái)訪問(wèn)私有成員:

大家廣泛認(rèn)為,Douglas Crockford是***個(gè)發(fā)現(xiàn)(或者可能說(shuō)發(fā)表更合適)使用閉包來(lái)模擬私有成員的人。他的網(wǎng)站,javascript.crockford.com,包含了JavaScript方面的大量信息 —— 對(duì)JavaScript感興趣的開發(fā)人員都應(yīng)該去他的網(wǎng)站看看。

  1. function Person(name, age) {  
  2.     var occupation;  
  3.     this.getOccupation = function() { return occupation; };  
  4.     this.setOccupation = function(newOcc) { occupation =   
  5.                          newOcc; };  
  6.     // accessors for name and age      

要注意的是,這樣的私有成員同我們所認(rèn)為的C#中的私有成員稍有不同。在C#中,類的公開方法可以直接訪問(wèn)類的私有成員。但是在JavaScript中,私有成員只有通過(guò)在閉包中包含有這些私有成員的方法來(lái)訪問(wèn)(這樣的方法通常稱為特權(quán)方法,因?yàn)樗鼈儾煌谄胀ǖ墓_方法)。因此,在Person的公開方法中,你依然可以通過(guò)Person的特權(quán)方法方法來(lái)訪問(wèn)私有成員:

  1. Person.prototype.somePublicMethod = function() {  
  2.     // doesn’t work!  
  3.     // alert(this.name);  
  4.     // this one below works  
  5.     alert(this.getName());  
  6. }; 

大家廣泛認(rèn)為,Douglas Crockford是***個(gè)發(fā)現(xiàn)(或者可能說(shuō)發(fā)表更合適)使用閉包來(lái)模擬私有成員的人。他的網(wǎng)站,javascript.crockford.com,包含了JavaScript方面的大量信息 —— 對(duì)JavaScript感興趣的開發(fā)人員都應(yīng)該去他的網(wǎng)站看看。

類的繼承

好的,現(xiàn)在你已經(jīng)看到了如何通過(guò)構(gòu)造器函數(shù)和原型對(duì)象在JavaScript中模擬類。你也已經(jīng)了解原型鏈可以確保所有的對(duì)象都能具有 Object.prototype中的通用方法。你還看到了如何使用閉包來(lái)模擬出私有成員。但是,這里好像還是缺點(diǎn)什么東西。你還沒(méi)看到在 JavaScript中如何實(shí)現(xiàn)類的繼承;這在C#中可是司空見慣的事情。很不幸,在JavaScript進(jìn)行類的繼承無(wú)法象在C#中那樣鍵入一個(gè)冒號(hào)而實(shí)現(xiàn);在JavaScript中還需要做更多的事情。但從另一方面講,因?yàn)镴avaScript非常靈活,我們有多種途徑實(shí)現(xiàn)類的繼承。

比方說(shuō),如圖9所示,你有一個(gè)基類叫Pet,它有一個(gè)派生類叫做Dog。怎樣在JavaScript中實(shí)現(xiàn)這個(gè)繼承關(guān)系呢?Pet類就很簡(jiǎn)單了,你已經(jīng)看到過(guò)怎么實(shí)現(xiàn)它了:


圖9 類 

  1. // class Pet  
  2. function Pet(name) {  
  3.     this.getName = function() { return name; };  
  4.     this.setName = function(newName) { name = newName; };  
  5. }  
  6.  
  7. Pet.prototype.toString = function() {  
  8.     return "This pet’s name is: " + this.getName();  
  9. };  
  10.  
  11. // end of class Pet  
  12. var parrotty = new Pet("Parrotty the Parrot");  
  13. alert(parrotty); 

那該如何定義派生自Pet類的Dog類呢?從 圖9中可看出,Dog類具有一個(gè)額外的屬性,breed,,并且它還重載了Pet的toString方法(請(qǐng)注意,avaScript中的方法和屬性命名慣例采用的是駝峰式大小寫方式,即camel case;而C#推薦使用的是Pascal大小寫方式)。 圖10所示即為Pet類的定義實(shí)現(xiàn)方法:

圖10 繼承Pet類

  1. // class Dog : Pet   
  2. // public Dog(string name, string breed)  
  3. function Dog(name, breed) {  
  4.     // think Dog : base(name)   
  5.     Pet.call(this, name);  
  6.     this.getBreed = function() { return breed; };  
  7.     // Breed doesn’t change, obviously! It’s read only.  
  8.     // this.setBreed = function(newBreed) { name = newName; };  
  9. }  
  10.  
  11. // this makes Dog.prototype inherits  
  12. // from Pet.prototype  
  13. Dog.prototype = new Pet();  
  14.  
  15. // remember that Pet.prototype.constructor  
  16. // points to Pet. We want our Dog instances’  
  17. // constructor to point to Dog.  
  18. Dog.prototype.constructor = Dog;  
  19.  
  20. // Now we override Pet.prototype.toString  
  21. Dog.prototype.toString = function() {  
  22.     return "This dog’s name is: " + this.getName() +   
  23.         ", and its breed is: " + this.getBreed();  
  24.  
  25. };  
  26.  
  27. // end of class Dog  
  28.  
  29. var dog = new Dog("Buddy""Great Dane");  
  30.  
  31. // test the new toString()  
  32. alert(dog);  
  33.  
  34. // Testing instanceof (similar to the is operator)  
  35.  
  36. // (dog is Dog)? yes  
  37. alert(dog instanceof Dog);  
  38.  
  39. // (dog is Pet)? yes  
  40. alert(dog instanceof Pet);  
  41.  
  42. // (dog is Object)? yes  
  43. alert(dog instanceof Object); 

通過(guò)正確設(shè)置原型鏈這個(gè)小把戲,就可以同在C#中所期望的那樣,使得instanceof測(cè)試在JavaScript中也能夠正常進(jìn)行。而且如你所愿,特權(quán)方法也能夠正常得以運(yùn)行。

#p#

模擬命名空間

在C++和C#中,命名空間用來(lái)將命名沖突的可能性減小到最小的程度。例如,在.NET框架中,命名空間可以幫助我們區(qū)分出 Microsoft.Build.Task.Message和Sys­tem.Messaging.Message這兩個(gè)類。JavaScript并沒(méi)有明確的語(yǔ)言特性來(lái)支持命名空間,但使用對(duì)象可以非常容易的模擬出命名空間。比如說(shuō)你想創(chuàng)建一個(gè)JavaScript代碼庫(kù)。不想在全局中定義函數(shù)和類,你就可以將你的函數(shù)和類封裝到如下這樣的命名空間之中:

  1. var MSDNMagNS = {};  
  2.  
  3. MSDNMagNS.Pet = function(name) { // code here };  
  4. MSDNMagNS.Pet.prototype.toString = function() { // code };  
  5.  
  6. var pet = new MSDNMagNS.Pet("Yammer"); 

只有一層命名空間可能會(huì)出現(xiàn)不唯一的請(qǐng)看,所以你可以創(chuàng)建嵌套的命名空間:

  1. var MSDNMagNS = {};  
  2.  
  3. // nested namespace "Examples"  
  4. MSDNMagNS.Examples = {};   
  5.  
  6. MSDNMagNS.Examples.Pet = function(name) { // code };  
  7. MSDNMagNS.Examples.Pet.prototype.toString = function() { // code };  
  8.  
  9. var pet = new MSDNMagNS.Examples.Pet("Yammer"); 

不難想象,每次都鍵入這些很長(zhǎng)的嵌套命名空間很快就會(huì)讓人厭煩。幸運(yùn)的是,你的代碼庫(kù)的用戶可以很容易地為你的命名空間起一個(gè)比較簡(jiǎn)潔的別名:

你要是看一眼Microsoft AJAX代碼庫(kù)的源代碼的話,就會(huì)發(fā)現(xiàn)該庫(kù)的編寫者也使用了類似的技巧來(lái)實(shí)現(xiàn)命名空間(請(qǐng)看靜態(tài)方法Type.registerNamespace的實(shí)現(xiàn)代碼)。這方面更詳細(xì)的信息可參見"OOP and ASP.NET AJAX"的側(cè)邊欄。

  1. // MSDNMagNS.Examples and Pet definition...  
  2. // think "using Eg = MSDNMagNS.Examples;"   
  3. var Eg = MSDNMagNS.Examples;  
  4. var pet = new Eg.Pet("Yammer");  
  5.  
  6. alert(pet); 

你應(yīng)該用這種方式來(lái)進(jìn)行JavaScript編程嗎?

如你所見,JavaScript對(duì)面向?qū)ο蟮闹С址浅:谩km然設(shè)計(jì)為基于原型的語(yǔ)言,但是它足夠靈活也足夠強(qiáng)大,允許你拿它來(lái)進(jìn)行通常是出現(xiàn)在其它常用語(yǔ)言中的基于類的編程風(fēng)格。但是問(wèn)題在于:你是否應(yīng)該以這種方式來(lái)進(jìn)行JavaScript編碼嗎?你是否應(yīng)該采用C#或C++的編程方式,采用比較聰明的方式模擬出本來(lái)不存在的特性來(lái)進(jìn)行JavaScript編程?每種編程語(yǔ)言都互不相同,一種語(yǔ)言的***實(shí)踐對(duì)另外一種編程語(yǔ)言來(lái)講可能就不實(shí)***的了。

你已經(jīng)了解在JavaScript中是對(duì)象繼承自對(duì)象(而非類繼承自類)。所以,讓大量的類使用靜態(tài)的繼承層次結(jié)構(gòu)可能不是JavaScript之道??赡芫拖驞ouglas Crockford在他的這篇文章"Prototypal Inheritance in JavaScript"中所說(shuō)的那樣,JavaScript的編程之道就是創(chuàng)建原型對(duì)象,并使用下面這樣的簡(jiǎn)單的對(duì)象函數(shù)來(lái)創(chuàng)建繼承自原對(duì)象的新對(duì)象:

  1. function object(o) {  
  2.         function F() {}  
  3.         F.prototype = o;  
  4.         return new F();  
  5.     } 

然后,既然JavaScript對(duì)象可塑性很強(qiáng),你就可以在對(duì)象生成之后,通過(guò)為它添加必要的新字段和新方法來(lái)增強(qiáng)對(duì)象。

這種做法都很不錯(cuò),但不可否認(rèn)的是,全世界大多數(shù)開發(fā)者都更加屬性基于類的編程。實(shí)際上,基于類的編程還會(huì)繼續(xù)流行下去。根據(jù)即將發(fā)布的ECMA-262 規(guī)范(ECMA-262是JavaScript的官方規(guī)范)的第4個(gè)版本,JavaScript 2.0將具有真正的類。所以說(shuō),JavaScript正在逼近基于類的編程語(yǔ)言。然而,JavaScript 2.0要得到廣泛使用可能還需要幾年的時(shí)間。 同時(shí)還有一點(diǎn)也很重要,就是要全面掌握當(dāng)前版本的JavaScript,只有這樣才能讀懂和編寫出基于原型和基于類的這兩種風(fēng)格的JavaScript代碼。

大局觀

隨著交互式、重客戶端AJAX應(yīng)用的普及,JavaScript很快就成為了.NET開發(fā)者工具箱中最有用的工具之一。然而,對(duì)于更加適應(yīng) C++、C#或者Visual Basic等語(yǔ)言的開發(fā)者來(lái)講,JavaScript的原型本性一開始會(huì)讓它們感到很不適應(yīng)。我覺(jué)得我的JavaScript之旅收獲頗豐,但一直以來(lái)也不乏挫折打擊。如果這篇文章能夠幫助你更加順利地進(jìn)步,那么我將倍感欣慰,因?yàn)檫@就是我寫這篇文章的目的所在。

OOP 和 ASP.NET AJAX

ASP.NET AJAX中實(shí)現(xiàn)的OOP同我在這篇文章里討論的規(guī)范的實(shí)現(xiàn)方法稍有不同。這里面主要有兩個(gè)方面的原因:ASP.NET AJAX版的實(shí)現(xiàn)為反射(對(duì)于象xml-scrip這樣的聲明式語(yǔ)法并且為了參數(shù)驗(yàn)證,反射是很有必要的手段)提供了更多的可能,而且ASP.NET AJAX旨在將.NET開發(fā)者所熟悉的其它一些語(yǔ)法結(jié)構(gòu),比如屬性、事件、枚舉以及接口等翻譯為JavaScript代碼。

在當(dāng)前廣泛可用的版本中,JavaScript缺乏.NET開發(fā)者所熟知的大量OOP方面的概念,ASP.NET AJAX模擬出了其中的大部分概念。

類可用具有基于命名規(guī)范的屬性訪問(wèn)器(下文中有例子),還可用完全按照.NET所提供的模式進(jìn)行事件多播。私有變量的命名遵從以下劃線打頭的成員就是私有成員這樣的規(guī)范。很少有必要使用真正私有的變量,這個(gè)策略使得我們可用從調(diào)試器中直接查看這種變量。引入接口也是為了進(jìn)行類型檢查,而不是通常的 duck-typing(一種類型方案,其基于的概念是,如果有一種東西象鴨子那樣走路并且象鴨子那樣嘎嘎叫,我們就認(rèn)為這種東西是鴨子,或者說(shuō)可用把這種東西看作鴨子)。

類和反射

在JavaScript中,我們無(wú)法得知函數(shù)的名字。即使有可能可以得知,多數(shù)情況下這對(duì)我們來(lái)說(shuō)也沒(méi)有什么幫助,因?yàn)轭悩?gòu)造器通常就是將一個(gè)匿名函數(shù)賦值為一個(gè)命名空間變量。真正的類型名的是由該變量的全限定名組成的,但卻同樣無(wú)法取得,構(gòu)造器函數(shù)對(duì)此名也一無(wú)所知。為了克服此局限并在 JavaScript類之中具有豐富的反射機(jī)制,ASP.NET AJAX要求要將類型的名字進(jìn)行注冊(cè)。

ASP.NET AJAX中的反射API可用于任何類型,無(wú)論該類型是內(nèi)建的類、接口、命名空間、甚至是枚舉都沒(méi)有問(wèn)題,而且其中還包含有和.NET框架中相同的 isInstanceOfType和inheritsFrom函數(shù),這兩個(gè)函數(shù)用來(lái)在程序運(yùn)行時(shí)對(duì)類的層次結(jié)構(gòu)進(jìn)行檢視。ASP.NET AJAX在調(diào)試模式還做了類型檢查,其意義在于能夠幫助開發(fā)者盡早地找出程序中的bug。

注冊(cè)類的層次結(jié)構(gòu)和基類的調(diào)用

要在ASP.NET AJAX中定義一個(gè)類,你需要將該類的構(gòu)造器函數(shù)賦值給一個(gè)變量(要注意構(gòu)造器函數(shù)是如何調(diào)用基類的方法的):

  1. MyNamespace.MyClass = function() {  
  2.  MyNamespace.MyClass.initializeBase(this);  
  3.  this._myProperty = null;  

然后,你需要在它的原型中定義該類的成員:

  1. MyNamespace.MyClass.prototype = {  
  2.  get_myProperty: function() { return this._myProperty;},  
  3.  set_myProperty: function(value) { this._myProperty = value; },  
  4.  doSomething: function() {  
  5.  MyNamespace.MyClass.callBaseMethod(this"doSomething");  
  6.  /* do something more */ 
  7.  }  

***,你要對(duì)這個(gè)類進(jìn)行注冊(cè):

  1. MyNamespace.MyClass.registerClass(  
  2.  "MyNamespace.MyClass ", MyNamespace.BaseClass); 

構(gòu)造器和原型的繼承層次結(jié)構(gòu)就不需要你管了,因?yàn)閞egisterClass函數(shù)會(huì)為你完成此項(xiàng)任務(wù)。

Bertrand Le Roy是ASP.NET AJAX團(tuán)隊(duì)中的一位二級(jí)軟件設(shè)計(jì)工程師,Software Design Engineer II。

Ray Djajadinata來(lái)自新加坡的Barclays Capital公司,他正興致高昂地從事著AJAX應(yīng)用的開發(fā)。你可以通過(guò)這個(gè)Email同他聯(lián)系: ray.djajadinata@gmail.com.

英文原文:Create Advanced Web Applications With Object-Oriented Techniques

譯文鏈接:http://www.oschina.net/translate/create-advanced-web-applications-with-object-oriented-techniques

【責(zé)任編輯:小林 TEL:(010)68476606】

責(zé)任編輯:林師授 來(lái)源: OSCHINA編譯
相關(guān)推薦

2009-09-22 12:59:07

ibmdwWeb

2010-02-26 14:40:15

Python應(yīng)用程序

2009-01-19 11:07:42

C#Web.NET

2024-01-05 07:38:55

2009-09-03 17:36:13

C#創(chuàng)建Web應(yīng)用程序

2010-07-22 08:54:14

jQuery

2012-05-14 17:35:28

移動(dòng)Web

2009-08-27 11:40:43

ibmdw云計(jì)算

2010-06-13 09:22:37

jQuery

2017-09-21 10:43:55

web程序語(yǔ)言

2009-09-22 12:25:04

ibmdwDB2

2011-08-18 09:47:42

2009-11-06 12:49:11

WCF面向服務(wù)

2009-01-03 14:25:10

ibmdwWeb

2021-09-07 10:24:36

Vue應(yīng)用程序Web Workers

2019-10-22 20:41:08

應(yīng)用程序服務(wù)器系統(tǒng)

2009-12-21 09:54:54

Web應(yīng)用程序安全測(cè)試

2010-05-20 09:48:36

2011-03-22 14:12:17

LAMP

2023-12-10 14:43:30

PythonGUIeel
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)