1. 程式人生 > >《Javascript高級程序設計》閱讀記錄(三):第五章 上

《Javascript高級程序設計》閱讀記錄(三):第五章 上

面向對象的語言 none 括號 數量 mas ie9 驗證 ive .so

  這個系列以往文字地址:

  《Javascript高級程序設計》閱讀記錄(一):第二、三章

  《Javascript高級程序設計》閱讀記錄(二):第四章

  這個系列,我會把閱讀《Javascript高級程序設計》之後,感覺講的比較深入,而且實際使用價值較大的內容記錄下來,並且註釋上我的一些想法。做這個一方面是提升了我的閱讀效果以及方便我以後閱讀

  另一個目的是,Javascript高級程序設計這本書內容很多也很厚,希望其他沒有時間的人可以通過看這系列摘錄,就可以大體學到書裏面的核心內容。

  綠色背景的內容是我認為比較值得註意的原著內容。

  黃色背景的內容是我認為非常重要的原著內容。

  我的理解會用藍色的字體標示出來。

  這章的內容較多,而且比較重要,我會分兩篇來記錄。這章比較重點的地方是函數部分,學完之後我感覺收獲很大。

  第5章 引 用 類 型 

  引用類型的值(對象)是引用類型的一個實例。在 ECMAScript 中, 引用類型是一種數據結構,用於將數據和功能組織在一起。它也常被稱為類,但這種稱呼並不妥當。盡管 ECMAScript從技術上講是一門面向對象的語言,但它不具備傳統的面向對象語言所支持的類和接口等基本結構。引用類型有時候也被稱為對象定義,因為它們描述的是一類對象所具有的屬性和方法。

  對象是某個特定引用類型的實例。新對象是使用 new 操作符後跟一個構造函數來創建的。

  構造函數本身就是一個函數,只不過該函數是出於創建新對象的目的而定義的。請看下面這行代碼:

var person = new Object();

  這行代碼創建了 Object 引用類型的一個新實例,然後把該實例保存在了變量 person 中。使用的構造函數是 Object,它只為新對象定義了默認的屬性和方法。 ECMAScript 提供了很多原生引用類型(例如 Object),以便開發人員用以實現常見的計算任務。

5.1 Object 類型

  到目前為止,我們看到的大多數引用類型值都是 Object 類型的實例;而且, Object 也是ECMAScript 中使用最多的一個類型。雖然 Object 的實例不具備多少功能,但對於在應用程序中存儲和傳輸數據而言,它們確實是非常理想的選擇。

  創建 Object 實例的方式有兩種。第一種是使用 new 操作符後跟 Object 構造函數,如下所示:

var person = new Object();

person.name = "Nicholas";

person.age = 29;

  另一種方式是使用對象字面量表示法。對象字面量是對象定義的一種簡寫形式,目的在於簡化創建包含大量屬性的對象的過程。下面這個例子就使用了對象字面量語法定義了與前面那個例子中相同的

person 對象:

var person = {

name : "Nicholas",

age : 29

};

雖然可以使用前面介紹的任何一種方法來定義對象,但開發人員更青睞對象字面量語法,因為這種語法要求的代碼量少,而且能夠給人封裝數據的感覺。實際上,對象字面量也是向函數傳遞大量可選參數的首選方式,例如:

技術分享
function displayInfo(args) {

var output = "";

if (typeof args.name == "string"){

output += "Name: " + args.name + "\n";

}

if (typeof args.age == "number") {

output += "Age: " + args.age + "\n";

}

alert(output);

}

displayInfo({

name: "Nicholas",

age: 29

});

displayInfo({

name: "Greg"

});
View Code

  在這個例子中,函數 displayInfo()接受一個名為 args 的參數。這個參數可能帶有一個名為 name或 age 的屬性,也可能這兩個屬性都有或者都沒有。在這個函數內部,我們通過 typeof 操作符來檢測每個屬性是否存在,然後再基於相應的屬性來構建一條要顯示的消息。然後,我們調用了兩次這個函數,每次都使用一個對象字面量來指定不同的數據。這兩次調用傳遞的參數雖然不同,但函數都能正常執行。

  這種傳遞參數的模式最適合需要向函數傳入大量可選參數的情形。一般來講,命名參數雖然容易處理,但在有多個可選參數的情況下就會顯示不夠靈活。最好的做法是對那些必需值使用命名參數,而使用對象字面量來封裝多個可選參數

  一般來說,訪問對象屬性時使用的都是點表示法,這也是很多面向對象語言中通用的語法。不過,在 JavaScript 也可以使用方括號表示法來訪問對象的屬性。在使用方括號語法時,應該將要訪問的屬性以字符串的形式放在方括號中,如下面的例子所示。

alert(person["name"]); //"Nicholas"

alert(person.name); //"Nicholas"

從功能上看,這兩種訪問對象屬性的方法沒有任何區別。但方括號語法的主要優點是可以通過變量來訪問屬性,例如:

var propertyName = "name";

alert(person[propertyName]); //"Nicholas"

如果屬性名中包含會導致語法錯誤的字符,或者屬性名使用的是關鍵字或保留字,也可以使用方括號表示法。例如:

person["first name"] = "Nicholas";

由於"first name"中包含一個空格,所以不能使用點表示法來訪問它。然而,屬性名中是可以包含非字母非數字的,這時候就可以使用方括號表示法來訪問它們。

通常,除非必須使用變量來訪問屬性,否則我們建議使用點表示法

5.2 Array 類型

  除了 Object 之外, Array 類型恐怕是 ECMAScript 中最常用的類型了。而且, ECMAScript 中的數組與其他多數語言中的數組有著相當大的區別。雖然 ECMAScript 數組與其他語言中的數組都是數據的有序列表,但與其他語言不同的是, ECMAScript 數組的每一項可以保存任何類型的數據。也就是說,可以用數組的第一個位置來保存字符串,用第二位置來保存數值,用第三個位置來保存對象,以此類推。而且, ECMAScript 數組的大小是可以動態調整的,即可以隨著數據的添加自動增長以容納新增數據。

  創建數組的基本方式有兩種。第一種是使用 Array 構造函數,如下面的代碼所示。

var colors = new Array();

如果預先知道數組要保存的項目數量,也可以給構造函數傳遞該數量,而該數量會自動變成 length屬性的值。例如,下面的代碼將創建 length 值為 20 的數組。

var colors = new Array(20);

也可以向 Array 構造函數傳遞數組中應該包含的項。以下代碼創建了一個包含 3 個字符串值的數組:

var colors = new Array("red", "blue", "green");

  創建數組的第二種基本方式是使用數組字面量表示法。數組字面量由一對包含數組項的方括號表示,多個數組項之間以逗號隔開,如下所示:

var colors = ["red", "blue", "green"]; // 創建一個包含 3 個字符串的數組

var names = []; // 創建一個空數組

  由於數組最後一項的索引始終是 length-1,因此下一個新項的位置就是 length。每當在數組末尾添加一項後,其 length 屬性都會自動更新以反應這一變化。換句話說,上面例子第二行中的colors[colors.length]為位置 3 添加了一個值,最後一行的 colors[colors.length]則為位置 4添加了一個值。當把一個值放在超出當前數組大小的位置上時,數組就會重新計算其長度值,即長度值等於最後一項的索引加 1,如下面的例子所示:

var colors = ["red", "blue", "green"]; // 創建一個包含 3 個字符串的數組

colors[99] = "black"; // (在位置 99)添加一種顏色

alert(colors.length); // 100

5.2.1 檢測數組

自從 ECMAScript 3 做出規定以後,就出現了確定某個對象是不是數組的經典問題。對於一個網頁,或者一個全局作用域而言,使用 instanceof 操作符就能得到滿意的結果:

if (value instanceof Array){

//對數組執行某些操作

}

  instanceof 操作符的問題在於,它假定只有一個全局執行環境。如果網頁中包含多個框架,那實際上就存在兩個以上不同的全局執行環境,從而存在兩個以上不同版本的 Array 構造函數。如果你從一個框架向另一個框架傳入一個數組,那麽傳入的數組與在第二個框架中原生創建的數組分別具有各自不同的構造函數。為了解決這個問題, ECMAScript 5 新增了 Array.isArray()方法。這個方法的目的是最終確定某個值到底是不是數組,而不管它是在哪個全局執行環境中創建的。這個方法的用法如下。

if (Array.isArray(value)){

//對數組執行某些操作

}

支持 Array.isArray()方法的瀏覽器有 IE9+、 Firefox 4+、 Safari 5+、 Opera 10.5+和 Chrome。要在尚未實現這個方法中的瀏覽器中準確檢測數組,請參考 22.1.1 節。

5.2.2 轉換方法

如前所述,所有對象都具有 toLocaleString()、 toString()和 valueOf()方法。其中,調用

數組的 toString()方法會返回由數組中每個值的字符串形式拼接而成的一個以逗號分隔的字符串。而調用 valueOf()返回的還是數組。實際上,為了創建這個字符串會調用數組每一項的 toString()方法。來看下面這個例子。

var colors = ["red", "blue", "green"]; // 創建一個包含 3 個字符串的數組

alert(colors.toString()); // red,blue,green

alert(colors.valueOf()); // red,blue,green

alert(colors); // red,blue,green

5.2.3 棧方法

ECMAScript 數組也提供了一種讓數組的行為類似於其他數據結構的方法。具體說來,數組可以表現得就像棧一樣,後者是一種可以限制插入和刪除項的數據結構。棧是一種 LIFO(Last-In-First-Out,後進先出)的數據結構,也就是最新添加的項最早被移除。而棧中項的插入(叫做推入)和移除(叫做彈出),只發生在一個位置——棧的頂部。 ECMAScript 為數組專門提供了 push()和 pop()方法,以便實現類似棧的行為。

push()方法可以接收任意數量的參數,把它們逐個添加到數組末尾,並返回修改後數組的長度。而pop()方法則從數組末尾移除最後一項,減少數組的 length 值,然後返回移除的項。

5.2.4 隊列方法

棧數據結構的訪問規則是 LIFO (後進先出),而隊列數據結構的訪問規則是 FIFO (First-In-First-Out,先進先出)。隊列在列表的末端添加項,從列表的前端移除項。由於 push()是向數組末端添加項的方法,因此要模擬隊列只需一個從數組前端取得項的方法。實現這一操作的數組方法就是 shift(),它能夠移除數組中的第一個項並返回該項,同時將數組長度減 1。結合使用 shift()和 push()方法,可以像使用隊列一樣使用數組

5.2.5 重排序方法

數組中已經存在兩個可以直接用來重排序的方法: reverse()和 sort()。有讀者可能猜到了,

reverse()方法會反轉數組項的順序。請看下面這個例子。

var values = [1, 2, 3, 4, 5];

values.reverse();

alert(values); //5,4,3,2,1

這裏數組的初始值及順序是 1、 2、 3、 4、 5。而調用數組的 reverse()方法後,其值的順序變成了5、 4、 3、 2、 1。這個方法的作用相當直觀明了,但不夠靈活,因此才有了 sort()方法。在默認情況下, sort()方法按升序排列數組項——即最小的值位於最前面,最大的值排在最後面。為了實現排序, sort()方法會調用每個數組項的 toString()轉型方法,然後比較得到的字符串,以確定如何排序。即使數組中的每一項都是數值, sort()方法比較的也是字符串,如下所示。

var values = [0, 1, 5, 10, 15];

values.sort();

alert(values); //0,1,10,15,5

  可見,即使例子中值的順序沒有問題,但 sort()方法也會根據測試字符串的結果改變原來的順序。因為數值 5 雖然小於 10,但在進行字符串比較時, "10"則位於"5"的前面,於是數組的順序就被修改了。

  不用說,這種排序方式在很多情況下都不是最佳方案。因此 sort()方法可以接收一個比較函數作為參數,以便我們指定哪個值位於哪個值的前面

5.2.6 操作方法

  ECMAScript 為操作已經包含在數組中的項提供了很多方法。其中, concat()方法可以基於當前數組中的所有項創建一個新數組。具體來說,這個方法會先創建當前數組一個副本,然後將接收到的參數添加到這個副本的末尾,最後返回新構建的數組。在沒有給 concat()方法傳遞參數的情況下,它只是復制當前數組並返回副本。如果傳遞給 concat()方法的是一或多個數組,則該方法會將這些數組中的每一項都添加到結果數組中。如果傳遞的值不是數組,這些值就會被簡單地添加到結果數組的末尾。

  下一個方法是 slice(),它能夠基於當前數組中的一或多個項創建一個新數組。 slice()方法可以接受一或兩個參數,即要返回項的起始和結束位置。在只有一個參數的情況下, slice()方法返回從該參數指定位置開始到當前數組末尾的所有項。如果有兩個參數,該方法返回起始和結束位置之間的項——但不包括結束位置的項。註意, slice()方法不會影響原始數組。請看下面的例子。

技術分享
var colors = ["red", "green", "blue", "yellow", "purple"];

var colors2 = colors.slice(1);

var colors3 = colors.slice(1,4);

alert(colors2); //green,blue,yellow,purple

alert(colors3); //green,blue,yellow
View Code

  下面我們來介紹 splice()方法,這個方法恐怕要算是最強大的數組方法了,它有很多種用法。

  splice()的主要用途是向數組的中部插入項,但使用這種方法的方式則有如下 3 種。

   刪除:可以刪除任意數量的項,只需指定 2 個參數:要刪除的第一項的位置和要刪除的項數。例如, splice(0,2)會刪除數組中的前兩項。

  插入:可以向指定位置插入任意數量的項,只需提供 3 個參數:起始位置、 0(要刪除的項數)和要插入的項。如果要插入多個項,可以再傳入第四、第五,以至任意多個項。例如,splice(2,0,"red","green")會從當前數組的位置 2 開始插入字符串"red"和"green"。

  替換:可以向指定位置插入任意數量的項,且同時刪除任意數量的項,只需指定 3 個參數:起始位置、要刪除的項數和要插入的任意數量的項。插入的項數不必與刪除的項數相等。例如,splice (2,1,"red","green")會刪除當前數組位置 2 的項,然後再從位置 2 開始插入字符串"red"和"green"。

  splice()方法始終都會返回一個數組,該數組中包含從原始數組中刪除的項(如果沒有刪除任何項,則返回一個空數組)。

5.2.7 位置方法

  ECMAScript 5 為數組實例添加了兩個位置方法: indexOf()和 lastIndexOf()。這兩個方法都接收兩個參數:要查找的項和(可選的)表示查找起點位置的索引。其中, indexOf()方法從數組的開頭(位置 0)開始向後查找, lastIndexOf()方法則從數組的末尾開始向前查找。

這兩個方法都返回要查找的項在數組中的位置,或者在沒找到的情況下返回1。在比較第一個參數與數組中的每一項時,會使用全等操作符;也就是說,要求查找的項必須嚴格相等(就像使用===一樣)。

5.2.8 叠代方法

  ECMAScript 5 為數組定義了 5 個叠代方法。每個方法都接收兩個參數:要在每一項上運行的函數和(可選的)運行該函數的作用域對象——影響 this 的值。傳入這些方法中的函數會接收三個參數:數組項的值、該項在數組中的位置和數組對象本身。根據使用的方法不同,這個函數執行後的返回值可能會也可能不會影響方法的返回值。以下是這 5 個叠代方法的作用。

every():對數組中的每一項運行給定函數,如果該函數對每一項都返回 true,則返回 true。

filter():對數組中的每一項運行給定函數,返回該函數會返回 true 的項組成的數組。

forEach():對數組中的每一項運行給定函數。這個方法沒有返回值。

map():對數組中的每一項運行給定函數,返回每次函數調用的結果組成的數組。

some():對數組中的每一項運行給定函數,如果該函數對任一項返回 true,則返回 true。

以上方法都不會修改數組中的包含的值。

5.2.9 歸並方法

  ECMAScript 5 還新增了兩個歸並數組的方法: reduce()和 reduceRight()。這兩個方法都會叠代數組的所有項,然後構建一個最終返回的值。其中, reduce()方法從數組的第一項開始,逐個遍歷到最後。而 reduceRight()則從數組的最後一項開始,向前遍歷到第一項。

  這兩個方法都接收兩個參數:一個在每一項上調用的函數和(可選的)作為歸並基礎的初始值。傳給 reduce()和 reduceRight()的函數接收 4 個參數:前一個值、當前值、項的索引和數組對象。這個函數返回的任何值都會作為第一個參數自動傳給下一項。第一次叠代發生在數組的第二項上,因此第一個參數是數組的第一項,第二個參數就是數組的第二項。

5.3 Date 類型

  ECMAScript 中的 Date 類型是在早期 Java 中的 java.util.Date 類基礎上構建的。為此, Date類型使用自 UTC(Coordinated Universal Time,國際協調時間) 1970 年 1 月 1 日午夜(零時)開始經過的毫秒數來保存日期。在使用這種數據存儲格式的條件下, Date 類型保存的日期能夠精確到 1970 年 1月 1 日之前或之後的 285 616 年。

要創建一個日期對象,使用 new 操作符和 Date 構造函數即可,如下所示。

var now = new Date();

5.3.1 繼承的方法

與其他引用類型一樣, Date 類型也重寫了 toLocaleString()、toString()和 valueOf()方法;

   但這些方法返回的值與其他類型中的方法不同。 Date 類型的 toLocaleString()方法會按照與瀏覽器設置的地區相適應的格式返回日期和時間。這大致意味著時間格式中會包含 AM 或 PM,但不會包含時區信息(當然,具體的格式會因瀏覽器而異)。而 toString()方法則通常返回帶有時區信息的日期和時間,其中時間一般以軍用時間(即小時的範圍是 0 到 23)表示。

5.4 RegExp 類型

ECMAScript 通過 RegExp 類型來支持正則表達式。使用下面類似 Perl 的語法,就可以創建一個正則表達式。

var expression = / pattern / flags ;

其中的模式(pattern)部分可以是任何簡單或復雜的正則表達式,可以包含字符類、限定符、分組、向前查找以及反向引用。每個正則表達式都可帶有一或多個標誌(flags),用以標明正則表達式的行為。

正則表達式的匹配模式支持下列 3 個標誌。

g:表示全局(global)模式,即模式將被應用於所有字符串,而非在發現第一個匹配項時立即停止;

i:表示不區分大小寫(case-insensitive)模式,即在確定匹配項時忽略模式與字符串的大小寫;

m:表示多行(multiline)模式,即在到達一行文本末尾時還會繼續查找下一行中是否存在與模式匹配的項。

因此,一個正則表達式就是一個模式與上述 3 個標誌的組合體。不同組合產生不同結果,如下面的例子所示。

技術分享
/*

* 匹配字符串中所有"at"的實例

*/

var pattern1 = /at/g;

/*

* 匹配第一個"bat"或"cat",不區分大小寫

*/

var pattern2 = /[bc]at/i;

/*

* 匹配所有以"at"結尾的 3 個字符的組合,不區分大小寫

*/

var pattern3 = /.at/gi;
View Code

與其他語言中的正則表達式類似,模式中使用的所有元字符都必須轉義。正則表達式中的元字符包括:( [ { \ ^ $ | ) ? * + .]}

這些元字符在正則表達式中都有一或多種特殊用途,因此如果想要匹配字符串中包含的這些字符,就必須對它們進行轉義。

下面給出幾個例子前面舉的這些例子都是以字面量形式來定義的正則表達式。另一種創建正則表達式的方式是使用RegExp 構造函數,它接收兩個參數:一個是要匹配的字符串模式,另一個是可選的標誌字符串。可以使用字面量定義的任何表達式,都可以使用構造函數來定義,如下面的例子所示。

技術分享
/*

* 匹配第一個"bat"或"cat",不區分大小寫

*/

var pattern1 = /[bc]at/i;

/*

* 與 pattern1 相同,只不過是使用構造函數創建的

*/

var pattern2 = new RegExp("[bc]at", "i");
View Code

  在此, pattern1 和 pattern2 是兩個完全等價的正則表達式。要註意的是,傳遞給 RegExp 構造函數的兩個參數都是字符串(不能把正則表達式字面量傳遞給 RegExp 構造函數)。由於 RegExp 構造函數的模式參數是字符串,所以在某些情況下要對字符進行雙重轉義。所有元字符都必須雙重轉義,那些已經轉義過的字符也是如此,例如\n(字符\在字符串中通常被轉義為\\,而在正則表達式字符串中就會變成\\\\)。下表給出了一些模式,左邊是這些模式的字面量形式,右邊是使用 RegExp 構造函數定義相同模式時使用的字符串。

技術分享

ECMAScript 5 明確規定,使用正則表達式字面量必須像直接調用 RegExp 構造函數一樣,每次都創建新的 RegExp 實例。 IE9+、 Firefox 4+和 Chrome 都據此做出了修改。

5.4.2 RegExp實例方法

  RegExp 對象的主要方法是 exec(),該方法是專門為捕獲組而設計的。 exec()接受一個參數,即要應用模式的字符串,然後返回包含第一個匹配項信息的數組;或者在沒有匹配項的情況下返回 null。

  返回的數組雖然是 Array 的實例,但包含兩個額外的屬性: index 和 input。其中, index 表示匹配項在字符串中的位置,而 input 表示應用正則表達式的字符串。在數組中,第一項是與整個模式匹配的字符串,其他項是與模式中的捕獲組匹配的字符串(如果模式中沒有捕獲組,則該數組只包含一項)。

請看下面的例子。

技術分享
var text = "mom and dad and baby";

var pattern = /mom( and dad( and baby)?)?/gi;

var matches = pattern.exec(text);

alert(matches.index); // 0

alert(matches.input); // "mom and dad and baby"

alert(matches[0]); // "mom and dad and baby"

alert(matches[1]); // " and dad and baby"

alert(matches[2]); // " and baby"
View Code

正則表達式的第二個方法是 test(),它接受一個字符串參數。在模式與該參數匹配的情況下返回true;否則,返回 false。在只想知道目標字符串與某個模式是否匹配,但不需要知道其文本內容的情況下,使用這個方法非常方便。因此, test()方法經常被用在 if 語句中,如下面的例子所示。

技術分享
var text = "000-00-0000";

var pattern = /\d{3}-\d{2}-\d{4}/;

if (pattern.test(text)){

alert("The pattern was matched.");

}
View Code

在這個例子中,我們使用正則表達式來測試了一個數字序列。如果輸入的文本與模式匹配,則顯示一條消息。這種用法經常出現在驗證用戶輸入的情況下,因為我們只想知道輸入是不是有效,至於它為什麽無效就無關緊要了。

RegExp 實例繼承的 toLocaleString()和 toString()方法都會返回正則表達式的字面量,與創

建正則表達式的方式無關。例如:

var pattern = new RegExp("\\[bc\\]at", "gi");

alert(pattern.toString()); // /\[bc\]at/gi

alert(pattern.toLocaleString()); // /\[bc\]at/gi

5.5 Function 類型

說起來 ECMAScript 中什麽最有意思,我想那莫過於函數了——而有意思的根源,則在於函數實際上是對象每個函數都是 Function 類型的實例,而且都與其他引用類型一樣具有屬性和方法。由於函數是對象,因此函數名實際上也是一個指向函數對象的指針,不會與某個函數綁定。函數通常是使用函數聲明語法定義的,如下面的例子所示。

function sum (num1, num2) {

return num1 + num2;

}

這與下面使用函數表達式定義函數的方式幾乎相差無幾。

var sum = function(num1, num2){

return num1 + num2;

};

以上代碼定義了變量 sum 並將其初始化為一個函數。有讀者可能會註意到, function 關鍵字後面沒有函數名。這是因為在使用函數表達式定義函數時,沒有必要使用函數名——通過變量 sum 即可以引用函數。另外,還要註意函數末尾有一個分號,就像聲明其他變量時一樣。由於函數名僅僅是指向函數的指針,因此函數名與包含對象指針的其他變量沒有什麽不同。換句話說,一個函數可能會有多個名字,如下面的例子所示。

function sum(num1, num2){

return num1 + num2;

}

alert(sum(10,10)); //20

var anotherSum = sum;

alert(anotherSum(10,10)); //20

sum = null;

alert(anotherSum(10,10)); //20

以上代碼首先定義了一個名為 sum()的函數,用於求兩個值的和。然後,又聲明了變量 anotherSum,並將其設置為與 sum 相等(將 sum 的值賦給 anotherSum)。註意,使用不帶圓括號的函數名是訪問函數指針,而非調用函數。此時, anotherSum 和 sum 就都指向了同一個函數,因此 anotherSum()也可以被調用並返回結果。即使將 sum 設置為 null,讓它與函數“斷絕關系”,但仍然可以正常調用anotherSum()

5.5.1 沒有重載(深入理解)

將函數名想象為指針,也有助於理解為什麽 ECMAScript 中沒有函數重載的概念。以下是曾在第 3章使用過的例子。

function addSomeNumber(num){

return num + 100;

}

function addSomeNumber(num) {

return num + 200;

}

var result = addSomeNumber(100); //300

顯然,這個例子中聲明了兩個同名函數,而結果則是後面的函數覆蓋了前面的函數。以上代碼實際上與下面的代碼沒有什麽區別。

var addSomeNumber = function (num){

return num + 100;

};

addSomeNumber = function (num) {

return num + 200;

};

var result = addSomeNumber(100); //300

通過觀察重寫之後的代碼,很容易看清楚到底是怎麽回事兒——在創建第二個函數時,實際上覆蓋了引用第一個函數的變量 addSomeNumber

5.5.2 函數聲明與函數表達式

本節到目前為止,我們一直沒有對函數聲明和函數表達式加以區別。而實際上,解析器在向執行環境中加載數據時,對函數聲明和函數表達式並非一視同仁。解析器會率先讀取函數聲明,並使其在執行任何代碼之前可用(可以訪問);至於函數表達式,則必須等到解析器執行到它所在的代碼行,才會真正被解釋執行。請看下面的例子。

alert(sum(10,10));

function sum(num1, num2){

return num1 + num2;

}

以上代碼完全可以正常運行。因為在代碼開始執行之前,解析器就已經通過一個名為函數聲明提升(function declaration hoisting)的過程,讀取並將函數聲明添加到執行環境中。對代碼求值時, JavaScript引擎在第一遍會聲明函數並將它們放到源代碼樹的頂部。所以,即使聲明函數的代碼在調用它的代碼後面, JavaScript 引擎也能把函數聲明提升到頂部。如果像下面例子所示的,把上面的函數聲明改為等價的函數表達式,就會在執行期間導致錯誤。

alert(sum(10,10));

var sum = function(num1, num2){

return num1 + num2;

};

以上代碼之所以會在運行期間產生錯誤,原因在於函數位於一個初始化語句中,而不是一個函數聲明。換句話說,在執行到函數所在的語句之前,變量 sum 中不會保存有對函數的引用;而且,由於第一行代碼就會導致“ unexpected identifier”(意外標識符)錯誤,實際上也不會執行到下一行。

除了什麽時候可以通過變量訪問函數這一點區別之外,函數聲明與函數表達式的語法其實是等價的。

5.5.3 作為值的函數

因為 ECMAScript 中的函數名本身就是變量,所以函數也可以作為值來使用。也就是說,不僅可以像傳遞參數一樣把一個函數傳遞給另一個函數,而且可以將一個函數作為另一個函數的結果返回。來看一看下面的函數。

function callSomeFunction(someFunction, someArgument){

return someFunction(someArgument);

}

這個函數接受兩個參數。第一個參數應該是一個函數,第二個參數應該是要傳遞給該函數的一個值。然後,就可以像下面的例子一樣傳遞函數了。

function add10(num){

return num + 10;

}

var result1 = callSomeFunction(add10, 10);

alert(result1); //20

function getGreeting(name){

return "Hello, " + name;

}

var result2 = callSomeFunction(getGreeting, "Nicholas");

alert(result2); //"Hello, Nicholas"

這裏的 callSomeFunction()函數是通用的,即無論第一個參數中傳遞進來的是什麽函數,它都會返回執行第一個參數後的結果。還記得吧,要訪問函數的指針而不執行函數的話,必須去掉函數名後面的那對圓括號因此上面例子中傳遞給 callSomeFunction()的是 add10 和 getGreeting,而不是執行它們之後的結果。

當然,可以從一個函數中返回另一個函數,而且這也是極為有用的一種技術。例如,假設有一個對象數組,我們想要根據某個對象屬性對數組進行排序。而傳遞給數組 sort()方法的比較函數要接收兩個參數,即要比較的值。可是,我們需要一種方式來指明按照哪個屬性來排序。要解決這個問題,可以定義一個函數,它接收一個屬性名,然後根據這個屬性名來創建一個比較函數,下面就是這個函數的定義。

function createComparisonFunction(propertyName) {

return function(object1, object2){

var value1 = object1[propertyName];

var value2 = object2[propertyName];

if (value1 < value2){

return -1;

} else if (value1 > value2){

return 1;

} else {

return 0;

}

};

}

這個函數定義看起來有點復雜,但實際上無非就是在一個函數中嵌套了另一個函數,而且內部函數前面加了一個 return 操作符。在內部函數接收到 propertyName 參數後,它會使用方括號表示法來取得給定屬性的值。取得了想要的屬性值之後,定義比較函數就非常簡單了。上面這個函數可以像在下面例子中這樣使用。

var data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}];

data.sort(createComparisonFunction("name"));

alert(data[0].name); //Nicholas

data.sort(createComparisonFunction("age"));

alert(data[0].name); //Zachary

  這裏,我們創建了一個包含兩個對象的數組 data。其中,每個對象都包含一個 name 屬性和一個age 屬性。在默認情況下, sort()方法會調用每個對象的 toString()方法以確定它們的次序;但得到的結果往往並不符合人類的思維習慣。因此,我們調用 createComparisonFunction("name")方法創建了一個比較函數,以便按照每個對象的 name 屬性值進行排序。而結果排在前面的第一項是 name為"Nicholas", age 是 29 的對象。然後,我們又使用了 createComparisonFunction("age")返回的比較函數,這次是按照對象的 age 屬性排序。得到的結果是 name 值為"Zachary", age 值是 28 的對象排在了第一位。

《Javascript高級程序設計》閱讀記錄(三):第五章 上