日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
使用面向對象技術創(chuàng)建高級Web應用程序

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

海晏ssl適用于網(wǎng)站、小程序/APP、API接口等需要進行數(shù)據(jù)傳輸應用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)公司的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:13518219792(備注:SSL證書合作)期待與您的合作!

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

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

面向對象的編程(OOP)這種方法廣泛用于多種JavaScript庫,采用這種方法可使代碼庫更加易于管理和維護。JavaScript支持OOP,但它的支持方式同流行的Microsoft? .NET框架下的C++、C#、Visual Basic?等語言完全不同,所以,大量使用這些語言的開發(fā)者起初可能會發(fā)現(xiàn),JavaScript中的OOP比較怪異,同直覺不符。我寫這篇文章就是要對JavaScript到底是如何支持面向對象編程的以及如何高效利用這種支進行面向對象的JavaScript開發(fā)進行深入討論。接下來讓我們開始談談對象(除了對象還能有別的嗎?)吧。

JavaScript對象是字典

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

 
 
 
 
  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);

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

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

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

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

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

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

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

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

或者甚至通過使用Function構造器,向下面這樣來定義它:

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

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

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

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

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

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

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

記住這一點后,為對象添加方法就簡單了,只要選擇一個函數(shù)名并把一個函數(shù)賦值為這個函數(shù)名即可。接下來我通過將三個匿名函數(shù)分別賦值給各自相應的方法名,為一個對象定義了三個方法:

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

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

#p#

圖2 “this”隨著對象的改變而改變

 
 
 
 
  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. var williamShakespeare = {
  7.     "memorableQuote": "It is a wise father that knows his own child.", 
  8.     "sayIt" : displayQuote
  9. };
  10. var markTwain = {
  11.     "memorableQuote": "Golf is a good walk spoiled.", 
  12.     "sayIt" : displayQuote
  13. };
  14. var oscarWilde = {
  15.     "memorableQuote": "True friends stab you in the front." 
  16.     // we can call the function displayQuote
  17.     // as a method of oscarWilde without assigning it 
  18.     // as oscarWilde’s method. 
  19.     //"sayIt" : displayQuote
  20. };
  21. williamShakespeare.sayIt(); // true, true
  22. markTwain.sayIt(); // he didn’t know where to play golf
  23. // watch this, each function has a method call()
  24. // that allows the function to be called as a 
  25. // method of the object passed to call() as an
  26. // argument. 
  27. // this line below is equivalent to assigning
  28. // displayQuote to sayIt, and calling oscarWilde.sayIt().
  29. displayQuote.call(oscarWilde); // ouch!

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

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

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

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

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

有構造函數(shù)但沒有類

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

 
 
 
 
  1. Dog spot = new Dog();

能夠得到一個對象,這個對象就是Dog類的一個實例。但在JavaScript中根本就沒有類。要想得到同類最近似的效果,可以象下面這樣定義一個構造器函數(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. var spot = new DogConstructor("Spot");
  10. spot.respondTo("Rover"); // nope
  11. spot.respondTo("Spot"); // yeah!

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

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

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

 
 
 
 
  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ù)就會對調(diào)用中關鍵字"this"所指的對象進行初始化。采用這種方式,你就可以為對象創(chuàng)建模版了!無論何時當你需要創(chuàng)建類似的對象時,你就可以用"new"來調(diào)用該構造器函數(shù),然后你就能夠得到一個完全初始化好的對象。這和類看上去非常相似,不是嗎?實際上,JavaScript中構造器函數(shù)的名字往往就是你想模擬的類的名字,所以上面例子中的構造函數(shù)你就可以直接命名為Dog:

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

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

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

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

原型(Prototype)

原型對象是JavaScript面向對象編程中的一個核心概念。原型這個名稱來自于這樣一個概念:在JavaScript中,所有對象都是通過對已有的樣本(也即,原型)對象進行拷貝而創(chuàng)建的。該原型對象的所有屬性和方法都會成為通過使用該原型的構造函數(shù)生成的對象的屬性和方法。你可以認為,這些對象從它們的原型中繼承了相應的屬性和方法。當你象這樣來創(chuàng)建一個新的Dog對象時

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

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

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

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

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

#p#

圖4對象同樣也具有它們原型的屬性

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


圖5 繼承自它們的原型的實例

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

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

 
 
 
 
  1. Dog.prototype = new Object();

所以,就如同Dog的實例繼承自Dog.prototype一樣,Dog.prototype繼承自Object.prototype。這就使得Dog的所有實例也都會繼承Object.prototype的方法和實例。

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

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

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

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

圖7 從原型中進行繼承

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

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

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

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

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

閉包

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

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

假如你需要基于一個簡單評判標準對一個數(shù)字序列進行過濾,該標準就是大于100的數(shù)字可以留下,但要把其它的所以數(shù)字都過濾掉。你可以編寫寫一個如圖8所示的函數(shù)。

圖8 基于謂詞(Predicate)對元素進行過濾

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

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

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

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

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

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

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

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

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

#p#

模擬私有屬性

好的,現(xiàn)在讓我們來看看在閉包的幫助下怎樣才能模擬出私有成員。函數(shù)中的私有變量通常在函數(shù)之外是訪問不到的。在函數(shù)執(zhí)行結束后,實際上局部變量就會永遠消失。然而,如果內(nèi)部函數(shù)捕獲了局部變量的話,這樣的局部變量就會繼續(xù)存活下去。 這個實情就是在JavaScript中模擬出私有屬性的關鍵所在。請看下面的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; };
  6. }

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

 
 
 
 
  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. alert(ray.getName() + " is now " + ray.getAge() + 
  8.       " years old.");

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

要注意的是,這樣的私有成員同我們所認為的C#中的私有成員稍有不同。在C#中,類的公開方法可以直接訪問類的私有成員。但是在JavaScript中,私有成員只有通過在閉包中包含有這些私有成員的方法來訪問(這樣的方法通常稱為特權方法,因為它們不同于普通的公開方法)。因此,在Person的公開方法中,你依然可以通過Person的特權方法方法來訪問私有成員:

大家廣泛認為,Douglas Crockford是***個發(fā)現(xiàn)(或者可能說發(fā)表更合適)使用閉包來模擬私有成員的人。他的網(wǎng)站,javascript.crockford.com,包含了JavaScript方面的大量信息 —— 對JavaScript感興趣的開發(fā)人員都應該去他的網(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    
  7. }

要注意的是,這樣的私有成員同我們所認為的C#中的私有成員稍有不同。在C#中,類的公開方法可以直接訪問類的私有成員。但是在JavaScript中,私有成員只有通過在閉包中包含有這些私有成員的方法來訪問(這樣的方法通常稱為特權方法,因為它們不同于普通的公開方法)。因此,在Person的公開方法中,你依然可以通過Person的特權方法方法來訪問私有成員:

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

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

類的繼承

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

比方說,如圖9所示,你有一個基類叫Pet,它有一個派生類叫做Dog。怎樣在JavaScript中實現(xiàn)這個繼承關系呢?Pet類就很簡單了,你已經(jīng)看到過怎么實現(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. Pet.prototype.toString = function() {
  7.     return "This pet’s name is: " + this.getName();
  8. };
  9. // end of class Pet
  10. var parrotty = new Pet("Parrotty the Parrot");
  11. alert(parrotty);

那該如何定義派生自Pet類的Dog類呢?從圖9中可看出,Dog類具有一個額外的屬性,breed,,并且它還重載了Pet的toString方法(請注意,avaScript中的方法和屬性命名慣例采用的是駝峰式大小寫方式,即camel case;而C#推薦使用的是Pascal大小寫方式)。圖10所示即為Pet類的定義實現(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. // this makes Dog.prototype inherits
  11. // from Pet.prototype
  12. Dog.prototype = new Pet();
  13. // remember that Pet.prototype.constructor
  14. // points to Pet. We want our Dog instances’
  15. // constructor to point to Dog.
  16. Dog.prototype.constructor = Dog;
  17. // Now we override Pet.prototype.toString
  18. Dog.prototype.toString = function() {
  19.     return "This dog’s name is: " + this.getName() + 
  20.         ", and its breed is: " + this.getBreed();
  21. };
  22. // end of class Dog
  23. var dog = new Dog("Buddy", "Great Dane");
  24. // test the new toString()
  25. alert(dog);
  26. // Testing instanceof (similar to the is operator)
  27. // (dog is Dog)? yes
  28. alert(dog instanceof Dog);
  29. // (dog is Pet)? yes
  30. alert(dog instanceof Pet);
  31. // (dog is Object)? yes
  32. alert(dog instanceof Object);

通過正確設置原型鏈這個小把戲,就可以同在C#中所期望的那樣,使得instanceof測試在JavaScript中也能夠正常進行。而且如你所愿,特權方法也能夠正常得以運行。

#p#

模擬命名空間

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

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

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

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

不難想象,每次都鍵入這些很長的嵌套命名空間很快就會讓人厭煩。幸運的是,你的代碼庫的用戶可以很容易地為你的命名空間起一個比較簡潔的別名:

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

 
 
 
 
  1. // MSDNMagNS.Examples and Pet definition...
  2. // think "using Eg = MSDNMagNS.Examples;" 
  3. var Eg = MSDNMagNS.Examples;
  4. var&
    新聞名稱:使用面向對象技術創(chuàng)建高級Web應用程序
    網(wǎng)站地址:http://m.5511xx.com/article/cdsgepe.html