1.創(chuàng)建高級對象 使用構(gòu)造函數(shù)來創(chuàng)建對象 構(gòu)造函數(shù)是一個(gè)函數(shù),調(diào)用它來例示并初始化特殊類型的對象??梢允褂?nbsp;new 關(guān)鍵字來調(diào)用一個(gè)構(gòu)造函數(shù)。下面給出了使用構(gòu)造函數(shù)的新示例。 var myObject = new Object(); // 創(chuàng)建沒有屬性的通用對象。 var myBirthday = new Date(1961, 5, 10); // 創(chuàng)建一個(gè) Date 對象。 var myCar = new Car(); // 創(chuàng)建一個(gè)用戶定義的對象,并初始化其屬性。 通過構(gòu)造函數(shù)將一個(gè)參數(shù)作為特定的 this 關(guān)鍵字的值傳遞給新創(chuàng)建的空對象。然后構(gòu)造函數(shù)負(fù)責(zé)為新對象執(zhí)行適應(yīng)的初始化(創(chuàng)建屬性并給出其初始值)。完成后,構(gòu)造函數(shù)返回它所構(gòu)造的對象的一個(gè)參數(shù)。 編寫構(gòu)造函數(shù) 可以使用 new 運(yùn)算符結(jié)合像 Object()、Date() 和 Function() 這樣的預(yù)定義的構(gòu)造函數(shù)來創(chuàng)建對象并對其初始化。面向?qū)ο蟮木幊唐鋸?qiáng)有力的特征是定義自定義構(gòu)造函數(shù)以創(chuàng)建腳本中使用的自定義對象的能力。創(chuàng)建了自定義的構(gòu)造函數(shù),這樣就可以創(chuàng)建具有已定義屬性的對象。下面是自定義函數(shù)的示例(注意 this 關(guān)鍵字的使用)。 function Circle (xPoint, yPoint, radius) { this.x = xPoint; // 圓心的 x 坐標(biāo)。 this.y = yPoint; // 圓心的 y 坐標(biāo)。 this.r = radius; // 圓的半徑。 } 調(diào)用 Circle 構(gòu)造函數(shù)時(shí),給出圓心點(diǎn)的值和圓的半徑(所有這些元素是完全定義一個(gè)獨(dú)特的圓對象所必需的)。結(jié)束時(shí) Circle 對象包含三個(gè)屬性。下面是如何例示 Circle 對象。 var aCircle = new Circle(5, 11, 99); 使用原型來創(chuàng)建對象 在編寫構(gòu)造函數(shù)時(shí),可以使用原型對象(它本身是所有構(gòu)造函數(shù)的一個(gè)屬性)的屬性來創(chuàng)建繼承屬性和共享方法。原型屬性和方法將按引用復(fù)制給類中的每個(gè)對象,因此它們都具有相同的值??梢栽谝粋€(gè)對象中更改原型屬性的值,新的值將覆蓋默認(rèn)值,但僅在該實(shí)例中有效。屬于這個(gè)類的其他對象不受此更改的影響。下面給出了使用自定義構(gòu)造函數(shù)的示例,Circle(注意 this 關(guān)鍵字的使用)。 Circle.prototype.pi = Math.PI; function ACirclesArea () { return this.pi * this.r * this.r; // 計(jì)算圓面積的公式為 ?r2。 } Circle.prototype.area = ACirclesArea; // 計(jì)算圓面積的函數(shù)現(xiàn)在是 Circle Prototype 對象的一個(gè)方法。 var a = ACircle.area(); // 此為如何在 Circle 對象上調(diào)用面積函數(shù)。 使用這個(gè)原則,可以給預(yù)定義的構(gòu)造函數(shù)(都具有原型對象)定義附加屬性。例如,如果想要能夠刪除字符串的前后空格(與 VBScript 的 Trim 函數(shù)類似),就可以給 String 原型對象創(chuàng)建自己的方法。 // 增加一個(gè)名為 trim 的函數(shù)作為 // String 構(gòu)造函數(shù)的原型對象的一個(gè)方法。 String.prototype.trim = function() { // 用正則表達(dá)式將前后空格 // 用空字符串替代。 return this.replace(/(^\s*)|(\s*$)/g, ""); } // 有空格的字符串 var s = " leading and trailing spaces "; // 顯示 " leading and trailing spaces (35)" window.alert(s + " (" + s.length + ")"); // 刪除前后空格 s = s.trim(); // 顯示"leading and trailing spaces (27)" window.alert(s + " (" + s.length + ")"); 2.遞歸 遞歸是一種重要的編程技術(shù)。該方法用于讓一個(gè)函數(shù)從其內(nèi)部調(diào)用其自身。一個(gè)示例就是計(jì)算階乘。0 的階乘被特別地定義為 1。 更大數(shù)的階乘是通過計(jì)算 1 * 2 * ...來求得的,每次增加 1,直至達(dá)到要計(jì)算其階乘的那個(gè)數(shù)。 下面的段落是用文字定義的計(jì)算階乘的一個(gè)函數(shù)。 “如果這個(gè)數(shù)小于零,則拒絕接收。如果不是一個(gè)整數(shù),則將其向下舍入為相鄰的整數(shù)。如果這個(gè)數(shù)為 0,則其階乘為 1。如果這個(gè)數(shù)大于 0,則將其與相鄰較小的數(shù)的階乘相乘。” 要計(jì)算任何大于 0 的數(shù)的階乘,至少需要計(jì)算一個(gè)其他數(shù)的階乘。用來實(shí)現(xiàn)這個(gè)功能的函數(shù)就是已經(jīng)位于其中的函數(shù);該函數(shù)在執(zhí)行當(dāng)前的這個(gè)數(shù)之前,必須調(diào)用它本身來計(jì)算相鄰的較小數(shù)的階乘。這就是一個(gè)遞歸示例。 遞歸和迭代(循環(huán))是密切相關(guān)的 ? 能用遞歸處理的算法也都可以采用迭代,反之亦然。確定的算法通??梢杂脦追N方法實(shí)現(xiàn),您只需選擇最自然貼切的方法,或者您覺得用起來最輕松的一種即可。 顯然,這樣有可能會出現(xiàn)問題??梢院苋菀椎貏?chuàng)建一個(gè)遞歸函數(shù),但該函數(shù)不能得到一個(gè)確定的結(jié)果,并且不能達(dá)到一個(gè)終點(diǎn)。這樣的遞歸將導(dǎo)致計(jì)算機(jī)執(zhí)行一個(gè)“無限”循環(huán)。下面就是一個(gè)示例:在計(jì)算階乘的文字描述中遺漏了第一條規(guī)則(對負(fù)數(shù)的處理) ,并試圖計(jì)算任何負(fù)數(shù)的階乘。這將導(dǎo)致失敗,因?yàn)榘错樞蛴?jì)算 -24 的階乘時(shí),首先不得不計(jì)算 -25 的階乘;然而這樣又不得不計(jì)算 -26 的階乘;如此繼續(xù)。很明顯,這樣永遠(yuǎn)也不會到達(dá)一個(gè)終止點(diǎn)。 因此在設(shè)計(jì)遞歸函數(shù)時(shí)應(yīng)特別仔細(xì)。如果懷疑其中存在著無限遞歸的可能,則可以讓該函數(shù)記錄它調(diào)用自身的次數(shù)。如果該函數(shù)調(diào)用自身的次數(shù)太多,即使您已決定了它應(yīng)調(diào)用多少次,就自動退出。 下面仍然是階乘函數(shù),這次是用 JScript 代碼編寫的。 // 計(jì)算階乘的函數(shù)。如果傳遞了 // 無效的數(shù)值(例如小于零), // 將返回 -1,表明發(fā)生了錯(cuò)誤。若數(shù)值有效, // 把數(shù)值轉(zhuǎn)換為最相近的整數(shù),并 // 返回階乘。 function factorial(aNumber) { aNumber = Math.floor(aNumber); // 如果這個(gè)數(shù)不是一個(gè)整數(shù),則向下舍入。 if (aNumber < 0) { // 如果這個(gè)數(shù)小于 0,拒絕接收。 return -1; } if (aNumber == 0) { // 如果為 0,則其階乘為 1。 return 1; } else return (aNumber * factorial(aNumber - 1)); // 否則,遞歸直至完成。 } 3.變量范圍 JScript 有兩種變量范圍:全局和局部。如果在任何函數(shù)定義之外聲明了一個(gè)變量,則該變量為全局變量,且該變量的值在整個(gè)持續(xù)范圍內(nèi)都可以訪問和修改。如果在函數(shù)定義內(nèi)聲明了一個(gè)變量,則該變量為局部變量。每次執(zhí)行該函數(shù)時(shí)都會創(chuàng)建和破壞該變量;且它不能被該函數(shù)外的任何事物訪問。 像 C++ 這樣的語言也有“塊范圍”。在這里,任何一對“{}”都定義新的范圍。JScript 不支持塊范圍。 一個(gè)局部變量的名稱可以與某個(gè)全局變量的名稱相同,但這是完全不同和獨(dú)立的兩個(gè)變量。因此,更改一個(gè)變量的值不會影響另一個(gè)變量的值。在聲明局部變量的函數(shù)內(nèi),只有該局部變量有意義。 var aCentaur = "a horse with rider,"; // aCentaur 的全局定義。 // JScript 代碼,為簡潔起見有省略。 function antiquities() // 在這個(gè)函數(shù)中聲明了一個(gè)局部 aCentaur 變量。 { // JScript 代碼,為簡潔起見有省略。 var aCentaur = "A centaur is probably a mounted Scythian warrior"; // JScript 代碼,為簡潔起見有省略。 aCentaur += ", misreported; that is, "; // 添加到局部變量。 // JScript 代碼,為簡潔起見有省略。 } // 函數(shù)結(jié)束。 var nothinginparticular = antiquities(); aCentaur += " as seen from a distance by a naive innocent."; /* 在函數(shù)內(nèi),該變量的值為 "A centaur is probably a mounted Scythian warrior, misreported; that is, ";在函數(shù)外,該變量的值為這句話的其余部分: "a horse with rider, as seen from a distance by a naive innocent." */ 很重要的一點(diǎn)是注意變量是否是在其所屬范圍的開始處聲明的。有時(shí)這會導(dǎo)致意想不到的情況。 tweak(); var aNumber = 100; function tweak() { var newThing = 0; // 顯式聲明 newThing 變量。 // 本語句將未定義的變量賦給 newThing,因?yàn)橐延忻麨?nbsp;aNumber 的局部變量。 newThing = aNumber; //下一條語句將值 42 賦給局部的 aNumber。aNumber = 42; if (false) { var aNumber; // 該語句永遠(yuǎn)不會執(zhí)行。 aNumber = 123; // 該語句永遠(yuǎn)不會執(zhí)行。 } // 條件語句結(jié)束。 } // 該函數(shù)定義結(jié)束。 當(dāng) JScript 運(yùn)行函數(shù)時(shí),首先查找所有的變量聲明, var someVariable; 并以未定義的初始值創(chuàng)建變量。如果變量被聲明時(shí)有值, var someVariable = "something"; 那么該變量仍以未定義的值初始化,并且只有在運(yùn)行了聲明行時(shí)才被聲明值取代,假如曾經(jīng)被聲明過。 JScript 在運(yùn)行代碼前處理變量聲明,所以聲明是位于一個(gè)條件塊中還是其他某些結(jié)構(gòu)中無關(guān)緊要。JScript 找到所有的變量后立即運(yùn)行函數(shù)中的代碼。如果變量是在函數(shù)中顯式聲明的 ? 也就是說,如果它出現(xiàn)于賦值表達(dá)式的左邊但沒有用 var 聲明 ? 那么將把它創(chuàng)建為全局變量。 復(fù)制、傳遞和比較數(shù)據(jù) 在 JScript 中,對數(shù)據(jù)的處理取決于該數(shù)據(jù)的類型。 按值和按引用的比較 Numbers 和 Boolean 類型的值 (true 和 false) 是按值來復(fù)制、傳遞和比較的。當(dāng)按值復(fù)制或傳遞時(shí),將在計(jì)算機(jī)內(nèi)存中分配一塊空間并將原值復(fù)制到其中。然后,即使更改原來的值,也不會影響所復(fù)制的值(反過來也一樣),因?yàn)檫@兩個(gè)值是獨(dú)立的實(shí)體。 對象、數(shù)組以及函數(shù)是按引用來復(fù)制、傳遞和比較的。 當(dāng)按地址復(fù)制或傳遞時(shí),實(shí)際是創(chuàng)建一個(gè)指向原始項(xiàng)的指針,然后就像拷貝一樣來使用該指針。如果隨后更改原始項(xiàng),則將同時(shí)更改原始項(xiàng)和復(fù)制項(xiàng)(反過來也一樣)。實(shí)際上只有一個(gè)實(shí)體;“復(fù)本”并不是一個(gè)真正的復(fù)本,而只是該數(shù)據(jù)的又一個(gè)引用。 當(dāng)按引用比較時(shí),要想比較成功,兩個(gè)變量必須參照完全相同的實(shí)體。例如,兩個(gè)不同的 Array 對象即使包含相同的元素也將比較為不相等。要想比較成功,其中一個(gè)變量必須為另一個(gè)的參考。要想檢查兩個(gè)數(shù)組是否包含了相同的元素,比較 toString() 方法的結(jié)果。 最后,字符串是按引用復(fù)制和傳遞的,但是是按值來比較的。請注意,假如有兩個(gè) String 對象(用 new String("something") 創(chuàng)建的),按引用比較它們,但是,如果其中一個(gè)或者兩者都是字符串值的話,按值比較它們。 注意 鑒于 ASCII和 ANSI 字符集的構(gòu)造方法,按序列順序大寫字母位于小寫字母的前面。例如 "Zoo" 小于 "aardvark"。如果想執(zhí)行不區(qū)分大小寫的匹配,可以對兩個(gè)字符串調(diào)用 toUpperCase() 或 toLowerCase()。 傳遞參數(shù)給函數(shù) 按值傳遞一個(gè)參數(shù)給函數(shù)就是制作該參數(shù)的一個(gè)獨(dú)立復(fù)本,即一個(gè)只存在于該函數(shù)內(nèi)的復(fù)本。即使按引用傳遞對象和數(shù)組時(shí),如果直接在函數(shù)中用新值覆蓋原先的值,在函數(shù)外并不反映新值。只有在對象的屬性或者數(shù)組的元素改變時(shí),在函數(shù)外才可以看出。 例如(使用 IE 對象模式): // 本代碼段破壞(覆蓋)其參數(shù),所以 // 調(diào)用代碼中反映不出變化。 function Clobber(param) { // 破壞參數(shù);在調(diào)用代碼中 // 看不到。 param = new Object(); param.message = "This will not work"; } // 本段代碼改變參數(shù)的屬性, // 在調(diào)用代碼中可看到屬性改變。 function Update(param) { // 改變對象的屬性; // 可從調(diào)用代碼中看到改變。 param.message = "I was changed"; } // 創(chuàng)建一個(gè)對象,并賦給一個(gè)屬性。 var obj = new Object(); obj.message = "This is the original"; // 調(diào)用 Clobber,并輸出 obj.message。注意,它沒有發(fā)生變化。 Clobber(obj); window.alert(obj.message); // 仍然顯示 "This is the original"。 // 調(diào)用 Update,并輸出 obj.message。注意,它已經(jīng)被改變了。 Update(obj); window.alert(obj.message); // 顯示 "I was changed"。 檢驗(yàn)數(shù)據(jù) 當(dāng)按值進(jìn)行檢驗(yàn)時(shí),是比較兩個(gè)截然不同的項(xiàng)以查看它們是否相等。通常,該比較是逐字節(jié)進(jìn)行的。當(dāng)按引用進(jìn)行檢驗(yàn)時(shí),是看這兩項(xiàng)是否是指向同一個(gè)原始項(xiàng)的指針。如果是,則比較結(jié)果是相等;如果不是,即使它們每個(gè)字節(jié)都包含完全一樣的值,比較結(jié)果也為不相等。 按引用復(fù)制和傳遞字符串能節(jié)約內(nèi)存;但是由于在字符串被創(chuàng)建后不能進(jìn)行更改,因此可以按值進(jìn)行比較。這樣可以檢查兩個(gè)字符串是否包含相同的內(nèi)容,即使它們是完全獨(dú)立產(chǎn)生的。 |
|