JavaScript權威指南--數組

知識要點

 

CXJ學習數組

 

數組是值的有序結合。每一個值叫作一個元素,而每一個元素在數組中都有一個位置,用數字表示,稱爲索引。javascript

javascript數組是無類型的:數組的元素能夠是任意類型,同個數組的元素類型不一樣也能夠,還能夠是對象或者嵌套其餘數組。java

javascript數組的索引是基於零的32位數值:第一個元素的索引爲0,最大的索引爲4 294 967 294(2的32次方-2),數組最大能容納4 294 967 295個元素。node

javascript數組是動態的:根據須要他們會增加或縮減,無需動態的去分配空間。web

javascript數組多是稀疏的:數組元素的索引不必定要連續的,針對稀疏數組,length比全部元素的索引要大。算法

1.建立數組

使用直接量建立數組是最簡單方法,在方括號中將數組元素用逗號隔開便可。數組直接量中的值不必定是要常量,它們能夠是任意的表達式,它能夠包含對象直接量或其餘數組直接量,若是省略數組直接量中的某個值,省略的值的元素將被賦予undefined。編程

調用構造函數Array()是建立數組的另外一種方法。用三種方法調用構造函數數組

  • 調用時沒有參數,建立一個空數組[];
  • 調用時有一個值是參數,它指定長度;
  • 顯式指定兩個或多個數組元素或者數組的一個非數組元素。

2.數組的讀與寫

使用[]操做符來訪問數組中的一個元素。數組的引用位於方括號的左邊。方括號中是一個返回非負整數值的任意表達式。使用該表達式能夠讀也能夠寫數組中的一個元素。瀏覽器

請記住:數組是對象的特殊形式。使用方括號訪問數組元素就像使用方括號訪問對象的屬性同樣。app

數組的特別之處在於,當使用小於2的32次方的非負數整數做爲屬性名時,數組會自動維護其length屬性值。函數式編程

清晰地區分數組的索引和數組的屬性名是很是有用的。全部的索引都是其屬性名,但只有0到(2的32次方-2)的整數屬性名纔是索引。全部的數組都是對象,能夠爲其建立任意名字的屬性。但若是使用的屬性是數組的索引,數組的特殊行爲就是將根據須要更新它們的length屬性值,若是不是數組的索引則不會更新length。

注意,可使用負數或非整數來索引數組。這種狀況下,數值轉換爲字符串。字符串做爲屬性名來用。既然名字不是非負整數,就只能當作常規的對象屬性,而非數組的全部。一樣,若是湊巧使用了是非負整數的字符串,它就當作數組索引,而非對象的屬性。當使用一個浮點數和一個整數相等時狀況也是同樣的:

a[-1.23] = true; //這將建立一個名爲"-1.23"的屬性,不會更新length
a["1000"] = 0; //這是數組的第1001個元素
a[1.000] //和a[1]相等

事實上數組索引僅僅是對象屬性名的一種特殊類型,這意味着javascript數組沒有「越界」錯誤的概念。當試圖查詢對象中不存在的屬性時,不會報錯。只會獲得undefined值。相似於對象,對於對象一樣存在這種狀況。

既然數組是對象 ,那麼它們能夠從原型中繼承元素。在ECMAScript5中,數組能夠定義元素的getter和setter方法。若是一個數組確實繼承了元素或使用了元素的getter和setter方法,你應該指望它使用非優化的代碼路徑:訪問這種數組的元素的時間會與常規對象屬性的查找時間相近。

3.稀疏數組

稀疏數組就是包含從0開始的不連續索引的數組。一般,數組的length屬性值表明了數組中元素的個數。若是數組是稀疏的,那麼length的值遠大於元素的個數。可使用Array構造函數或簡單地指定數組的索引值大於當前數組長度來建立稀疏數組。

a = new Array(5);//數組中沒有元素,但a.length值是5
a = []; //建立一個空數組,length的值爲0
a[1000] = 0;//賦值添加一個元素,但設置的length值爲1001
5 in a     //false
1000 in a   //true

足夠稀疏的數組一般在實現上比稠密是數組更慢、內存利用率更高,在這樣的數組中查找元素的時間與常規對象屬性的查找時間同樣長。

勘誤:注意,當數組直接量中省略值時不會建立稀疏數組。省略的元素在數組中是存在的,其值爲undefined。這和數組元素根本不在是有一些微妙的區別的。能夠用in操做符檢測二者的區別。這句有錯

當使用for/in循環時,a1和a2的區別也很明顯。

須要注意的是,當省略數組直接量中的值時(使用連續的逗號,好比[1,,,,3]),這時獲得的也是稀疏數組,省略掉的值是不存在的:

var a1 = [, ]; //此時數組沒有元素,長度爲1
var a2 = [undefined]; //此時數組包含一個值爲undefined的元素
0 in a1; //=>false: a1在索引0處沒有元素
0 in a2; //=>true: a2在0處有一個值爲undefined的元素

在一些舊版的實現中,如(firefox3),在存在連續逗號的狀況下,插入undefined值的操做與此不一樣,在這些實現中,[1,,3]和[1,undefined,3]是如出一轍的。

4.數組的長度

每一個數組都有一個length屬性。就是這個屬性使其區別於常規的javascript。稠密數組和稀疏數組的比較。

在數組中,確定找不到一個元素的索引值大於它的長度。爲了維持此規則不變,數組有兩個特殊行爲。第一個如同上面的描述:若是爲一個數組元素賦值,它的索引i大於或等於現有的數組的長度時,length的值將設置爲i+1。第二個特殊行爲就是設置length屬性爲一個小於當前長度的非負整數n時,當前數組中的那些索引值大於或等於n的元素將從中刪除。

還能夠將數組的length屬性值設置爲大於當前的長度。實際上不會向數組中添加新的元素,它只是在的尾部建立一個空的區域。

在ECMAScript5中,可使用Object.defineProperty()將數組的屬性變成只讀的:

var a = [1, 2, 3];
Object.defineProperty(a, "length", {writable: false});     //讓length屬性只讀
a.length = 0;   //a不會改變

相似的,若是讓一個數組元素不能配置,就不能刪除它。若是不能刪除它,length的屬性不能設置小於不可配置元素的索引值(見前文的Object.seal()和Object.freeze()方法)。

5.數組元素的添加和刪除

添加數組元素最簡單的方法:爲新索引賦值;可使用push()方法在數組的末尾增長一個或多個元素,在數組的末尾壓入一個元素與給數組a[a.length]賦值是同樣的;可使用unshift()方法給首部插入一個元素,而且將其餘元素移動到更高的索引處。

能夠像刪除對象屬性同樣使用delete運算符來刪除數組元素:

a = [1, 2, 3];
delete a[1];
1 in a; //=>false:數組索引1並未在數組中定義
a.length; //=>3: delete操做並不影響數組的長度

刪除數組元素與爲其賦值undefined值是相似的(但有一些微妙的區別)。對一個數組元素使用delete不會修改數組的length屬性;也不會將元素從高索引處移下來填充已經刪除的元素空白。若是從一個數組中刪除一個元素,它就變成稀疏數組。

數組由pop()方法(它和push一塊兒使用),後者使減小長度1並返回刪除元素的值。還有一個shift()方法(它和unshift()一塊兒使用),從數組頭部刪除一個元素。和delete不一樣的是shift()方法將全部的元素下移到比當前元素索引低1的地方。

6.數組遍歷

使用for循環是遍歷數組的經常使用方法:

var keys = Object.keys(o); //得到o對象屬性名組成的數組
var values = []; //在樹組中存儲匹配屬性的值
for (var i = 0; i < keys.length; i++) { //對於數組中的每一個索引
    var key = keys[i]; //得到索引處的鍵值
    values[i] = o[key]; //在values數組中保存屬性值
}

在嵌套循環或其餘它性能很是重要的上下文中,能夠看到這種基本的數組遍歷須要優化,數組的長度應該只是查詢一次而非每次循環要查詢:len = keys.length。

假如這些數組是稠密的,而且全部的元素都是合法數據。不然,使用數組元素之間都應該檢測他們,若是想要排除null、undefined和不存在的元素

for (var i = 0; i < keys.length; i++) {
    if (!keys[i]) continue;//循環體
}

若是隻想跳過undefined和不存在的元素:

for (var i = 0; i < keys.length; i++) {
     if (!keys[i] === undefined) continue; //跳過undefined和不存在的元素
    //循環體    
}

最後,若是隻想跳過不存在的元素而仍然要處理存在的undefined的元素:

for (var i = 0; i < keys.length; i++) {
    if (!(i in keys)) continue; //跳過不存在的元素
}

還可使用for/in循環,處理稀疏數組。循環每次將一個可枚舉的屬性名(包括數組索引)賦值給循環變量,不存在的索引將不會遍歷到:

for (var index in sparseArray) {
    var value = sparseArray[index];
    //此處可以使用索引和值作一些事情
}

在前面章節已經注意到for/in循環可以枚舉繼承的屬性名,如添加到Array.prototype的方法。因爲這個緣由,在數組上不該該使用for/in循環,除非使用額外的檢測方法來過濾掉不想要的屬性。以下代碼:取一便可:

for (var i in a) {
    if (!a.hasOwnProperty(i)) continue; //跳過繼承的屬性
    //循環體
}

for (var i in a) {
    //跳過不是非負整數的i
    if (String(Math.floor(Math.abs(Number(i)))) !== i) continue;
}

ECMAScript規範容許for/in循環以不一樣的順序遍歷對象的屬性。一般數組元素的遍歷實現是升序的,但不能保證必定是這樣的。特別地,若是數組同時擁有對象屬性和數組元素,返回的屬性名極可能是按照建立的順序而非數組的大小順序。如何處理這個問題的實現各不相同,若是 算法依賴於遍歷的順序,那麼最好不要使用for/in循環而使用常規的for循環。

ECMAScript5定義了一些遍歷數組元素的新方法,按照索引的順序按個傳遞給定義的一個函數。這些方法中最經常使用的就是forEach()方法。

var data = [1, 2, 3, 4, 5];
var sumOfSquares = 0; //獲得數據的平方和
data.forEach(function(x) { //把每一個元素傳遞給此函數
    sumOfSquares += x * x; //平方相加
});
sumOfSquares; //=>55: 1+4+9+16+25

forEach()和相關的遍歷方法使得數組擁有擁有簡單而強大的函數式編程風格。

7.多維數組

javascript不支持真正的多維數組,但能夠用數組的數組來近似。訪問數組的數組中的元素,只要簡單地使用兩次[]操做符便可。

 //建立一個多維數組
var table = new Array(10); //表格10行
for (var i = 0; i < table.length; i++)
    table[i] = new Array(10); //每行10列
 //初始化數組
for (var row = 0; row < table.length; row++) {
    for (col = 0; col < table[row].length; col++) {
        table[row][col] = row * col;
    }
}
 //使用多維數組來計算(查詢)8*9
table[8][9]; //=>72

8.數組方法

ECMAScript3在Array.prototype中定義了一些頗有用的操做數組的函數,這意味着這些函數做爲人和數組的方法都是可用的。

8.1.join()

Array.join()方法將數組中全部元素都轉化爲字符串並鏈接在一塊兒,返回最後生成的字符串。能夠指定一個可選的字符串在生成的字符串中來分隔數組的各個元素。如過不指定分隔符,則使用默認的逗號。

var a = [2131, 551, 235, "hello", 123]
a.join(); //=>2131,551,235,hello,123
a.join(" "); //=> 2131 551 235 hello 123
a.join(""); //=>2131551235hello123
a.join("-"); //=> 2131-551-235-hello-123
var b = new Array(10);
b.join('-'); //=>---------9個連字號組成的字符串

Array.join()方法是String.split()方法的逆向操做,後者是將字符串分隔開來建立一個數組。

8.2.reverse()

Array.reverse()方法將數組的元素顛倒順序,返回逆序數組。它採起了替換;換句話說,它不經過從新排列的元素建立新的數組,而是在原先的數組中從新排列它們。

8.3.sort()

Array.sort()方法將數組中的元素排序後並返回排序後的數組。當不帶參數調用sort()時,數組元素以字母表順序排序(若有必要將臨時轉化爲字符串進行比較):

var a = new Array("banana", "cheery", "apple");
a.sort(); //=> ["apple", "banana", "cheery"]
var s = a.join(","); //=>"apple,banana,cheery"

若是數組包含undefined元素,它們會被排除到數組的尾部。

爲了按照其它方式而非字母順序表屬性進行數組排序,必須給sort()方法傳遞一個比較函數。該函數決定了它的兩個參數在排好序的數組中的前後順序。假設第一參數應該在前,比較函數應該返回一個小於0的數值。而且,假設兩個值相等(也就是說他們的屬性可有可無),函數應該返回0。所以,例如,用數值大小而非字母表順序進行數組排序。代碼以下:

var a = [33, 4, 1111, 222, 45555];
a.sort(); //=>[1111, 222, 33, 4, 45555] :字母表順序
a.sort(function(a, b) { //=>[4, 33, 222, 1111, 45555] 數值順序
return a - b; //根據數據,返回負數,0,正數
});
a.sort(function(a, b) {return b - a}); //=> [45555, 1111, 222, 33, 4] 數值大小相反的順序

注意:這裏使用匿名函數表達式很是方便。既然比較函數只使用了一次,就不必給它們命名了。

另外一個數組元素排序的例子 ,也許須要對一個字符串數組執行不區分大小寫的字母表排序,比較函數首先將參數都轉換爲小寫字符串(toLowerCase()方法),再開始比較:

a = ['ant', 'Bug', 'cat', 'Dog']
a.sort(); //=> ["Bug", "Dog", "ant", "cat"]:區分大小寫的排序
a.sort(function(s, t) { //不區分大小寫排序
var a = s.toLowerCase();
var b = t.toLowerCase();
if (a < b) return -1;
if (a > b) return 1;
return 0;
}); //=> ["ant", "Bug", "cat", "Dog"]

8.4.concat()

Array.concat()方法建立並返回一個新數組,它的元素包含調用concat()原始數組的元素和concat()的每一個參數。若是這些參數中任何一個自身是數組,則鏈接的是數組的元素,而非數組自己。但要注意:concat()不會遞歸扁平化數組的數組。concat()也不會修改調用的屬性。

var a = [1, 2, 3];
a.concat(4, 5); //=> [1, 2, 3, 4, 5]
a.concat([4, 5]); //=>[1, 2, 3, 4, 5]
a.concat([4, 5], [6, 7]); //=>[1, 2, 3, 4, 5, 6, 7]
a.concat(4, [5, [6, 7]]); //=>[1, 2, 3, 4, 5, [6,7]]

8.5.slice()

Array.slice()方法返回指定數組的一個片斷或子數組。它的兩個參數分別指定了片斷的開始和結束的位置。返回的數組包含第一個參數和全部到但不含第二個參數定製的位置之間的全部數組元素。若是隻指定一個參數,則返回數組將從開始的位置到數組結尾的全部元素。若是參數中出現了負數,它表示相對於數組中最後一個元素的位置。例如參數「-1」指定了最後一個元素,而-3指定了倒數第三個元素。注意slice()不會修改調用的數組。

var a = [1, 2, 3, 4, 5];
a.slice(0, 3); //=> [1, 2, 3]
a.slice(3); //=> [4, 5]
a.slice(1, -1); //=> [2, 3, 4]
a.slice(-3, -2); //=> [3]

8.6.splice()

Array.splice()方法是在數組中插入或刪除元素的通用方法。不一樣於slice()和concat(),splice()會修改調用的數組。

splice()可以從數組中刪除元素,插入元素到數組中或者同時完成這兩種操做。在插入或刪除點以後的數組元素會根據須要增長或減小它們的索引值,所以數組的其它部分仍然保持連續。

splice()第一個參數指定了插入(或)刪除的起始位置。第二個參數指定了應該從數組中刪除的元素的個數。若是省略第二個參數,從起點開始到數組結尾的全部元素都將被刪除。splice()返回一個由刪除元素組成的數組,或者沒有刪除就返回一個空數組。

var a = [1, 2, 3, 4, 5, 6, 7, 8];
a.splice(4); //=> [5, 6, 7, 8] ,a是 [1, 2, 3, 4]
a.splice(1,2) //=> [2, 3] ,a是 [1, 4]
a.splice(1,1) //=>[4],a是[1]

splice()的前兩個參數指定了要刪除的數組的元素。緊隨其後的任意個參數指定了須要插入數組中的元素,從第一個參數指定的位置開始插入。例如

var a = [1, 2, 3, 4, 5];
a.splice(2,0,'a','b')//=>返回[],a的值是[1, 2, "a", "b", 3, 4, 5]
a.splice(2,2,[1,2],3)//=>返回 ["a", "b"],a的值是[1,2,[1,2],3,3,4,5]

注意區別於concat(),splice()會插入數組自己而非數組的元素。

8.7.push()和pop()

push()和pop()方法容許將數組當作棧來使用。push()方法在數組的尾部添加一個或多個元素,並返回新的數組長度。pop()方法相反:它刪除數組的最後一個元素,減少數組長度並返回它刪除的值。注意:兩個方法都修改並替換原始數組而非生成一個修改版的新數組。

var stack = []; //stack:[]
stack.push(1, 2); //stack:[1,2] 返回2
stack.pop(); //stack:[1] //返回2
stack.push(3); //stack:[1,3] //返回2
stack.pop(); //stack:[1] //返回3
stack.push([4, 5]); //stack:[1,[4,5]] //返回2
stack.pop() //stack:[1] //返回[4,5]
stack.pop() //stcck:[] //返回1

8.8.unshift()和shift()

unshfit()在數組的頭部添加一個或多個元素,並將已存在的元素移動到更高的索引的位置來得到足夠的空間,最後返回數組的新長度。shift()刪除數組的第一個元素並將其返回。而後並把全部的元素下移一個位置來填補數組頭部的空缺。

var a = []; //a:[]
a.unshift(1); //a:[1] 返回1
a.unshift(22); //a:[22,1] 返回2
a.shift(); //a:[1] 返回22
a.unshift(3, [4, 5]); //a:[3,[4,5],1] 返回3
a.shift(); //a:[[4,5],1] 返回3
a.shift(); //a:[1] 返回[4,5]
a.shift(); //a:[] 返回1

注意:當使用多個參數調用unshift()時它的行爲使人驚訝。參數是一次性插入(就像splice()方法),而非一次一個。這意味着最終的數組中插入的元素的順序和他們在參數列表中的順序一致。而加入元素一個一個插入,它們的順序應該是反過來的。

8.9.toString()和toLocaleString()

該方法將其每一個元素轉化爲字符串(若有必要調用元素的toString() 方法),而且輸出逗號分隔的字符串列表。注意,輸出不包括方括號或其它任何形式的包裹數值的分隔符。例如:

[1, 2, 3, "hello"].toString(); //=>'1,2,3,hello'
["a", "b", "c", "d"].toString(); //=>'a,b,c,d'
[1, 2, [4, 5, 6], 7].toString(); //=>'1,2,4,5,6,7'

注意,這裏與不使用任何參數的join()方法返回的字符串是同樣的。

toLocaleString()是toString()方法的本地化版本。它調用元素的toLocaleString()方法將每一個數組元素轉化爲字符串,並使用本地化(和自定義實現的)分隔符將這些字符串鏈接起來生成最終的字符串。

9.ECMAScript5中的數組方法

ECMAScript5定義了9個新的方法來遍歷、映射、過濾、檢測、簡化和搜索數組。

首先,大多數方法的第一個參數接收一個函數,而且對數組的每一個元素(或幾個元素)調用一次該函數。若是是稀疏數組,對不存在的元素不調用傳遞的函數。 在大多數狀況下,調用提供的函數提供三個參數:數組元素、元素的索引和數組自己。 一般,只須要第一個參數值,第二個參數是可選的。若是有兩個參數,則調用的函數被看作是第二個參數的方法。也就是說:在調用函數時候傳遞進去的第二個參數做爲它的this關鍵字值來使用。被調用函數的返回值很是重要,可是不一樣的方法處理返回值的方式也不同。ECMAScript5中的數組方法都不會修改他們調用的原始數組。固然傳遞這些方法的函數是能夠修改這些數組的。

9.1.forEach()方法

forEach()方法從頭至尾遍歷數組,爲每一個元素調用指定的函數。如上所述:傳遞的函數做爲forEach()的第一個參數。而後forEach()使用3個個參數調用該函數:數組元素、元素的全部和數組自己。若是隻關心數組元素的值,能夠編寫只有一個參數的函數——額外的參數將忽略。

var data = [1, 2, 3, 4, 5]; //要 求和的數組
 //計算數組的和
var sum = 0; //初始值0
data.forEach(function(value) {
    sum += value; //將每一個值累加到sum上
});
sum; //=>15
 //給每一個數組元素自加1
data.forEach(function(v, i, a) {
    a[i] = v + 1;
});
data; //=> [2, 3, 4, 5, 6]

注意的是:forEach()沒法在全部元素都傳遞給調用的函數以前終止遍歷。也就是說,沒有像for循環中使用的相應的break語句。若是要提早終止。必須把forEach()方法放在一個try塊中,並能拋出一個異常。若是forEach()調用的函數能拋出foreach.break異常。循環會提早終止:

function foreach(a, f, t) {
    try {
        a.forEach(f, t);
    } catch (e) {
        if (e === foreach.break) return;
        else throw e;
    }
}
foreach.break = new Error("StopIteration");

9.2.map()方法

map()方法調用的數組的每一個元素傳遞給指定的函數,並返回一個數組,它包含該函數的返回值。例如:

a = [1, 2, 3]
b = a.map(function(x) {
    return x * x;
});
b; //=> [1, 4, 9]

傳遞給map()的函數調用方式和傳遞給forEach的調用方式同樣。但傳遞給map()的函數應該有返回值。注意:map()返回是的新數組,它不修改調用的數組。若是是稀疏數組,返回的也是相同方式的稀疏數組:它具備相同的長度,相同的缺失元素。

9.3.filter()

filter()方法返回的數組元素是調用的數組的一個子集。傳遞的函數是用來邏輯斷定的:該函數返回true或false。調用斷定函數就像調用forEach()和map()同樣。若是返回值爲true或能轉化爲true的值,那麼傳遞給斷定函數的元素就是這個子集的成員,它將被添加到一個做爲返回值的數組中。例如:

a = [5, 4, 3, 2, 1];
smallvalues = a.filter(function(x) {
    return x < 3
}); //=> [2, 1]
everyother = a.filter(function(x, i) {
    return i % 2 == 0
});//=> [5, 3, 1]

注意:filter()跳過稀疏數組中缺乏的元素,它返回的數組老是稠密的。爲了壓縮稀疏數組的空缺。代碼以下:

var dense = sparse.filter(function() {
    return true;
});

甚至壓縮空缺並刪除undefined和null元素,能夠這樣使用filter():

a = a.filter(function(x) {
    return x !== undefined && x != null;
});

9.4.every()和some()

every()和some()方法是數組的邏輯斷定:他們對數組元素應用指定的函數進行斷定,返回true或false。

every()函數就像數學中的「徵對全部」的量詞∀ :當且僅當徵對數組中的全部元素調用斷定函數都返回true,它才返回true:

var a = [1, 2, 3, 4, 5];
a.every(function(x) {return x < 10;}); //=>true:全部的值都小於10
a.every(function(x) {return x % 2 === 0;}); //=>fasle :不是全部的值都是偶數

some()方法就像數學中的"存在"量詞彐:當數組中只要有一個元素調用斷定函數返回true,它就返回true;而且當且僅當數值中全部元素斷定調用函數都返回false,它才返回false.

var a = [1, 2, 3, 4, 5];
a.some(function(x) {return x % 2 === 0;}); //true :a含有偶數值
a.some(isNaN) //=>false a不包含非數值元素

注意,一旦every()和some()確認該返回什麼值什麼值它們就會中止遍歷數組元素。some()在斷定函數第一次返回true後就返回true,但若是斷定函數一直返回false,它將遍歷整個數組。every()剛好相反:它在 斷定函數第一次返回false後就返回false,但若是斷定函數一直返回true,它將遍歷整個數組。注意:根據數學上的慣例,在空數組時,every()返回true,some()返回false。

9.5.reduce()和reduceRight()

reduce()和reduceRight()方法使用指定的函數將數組元素進行組合,生成單個值。這在函數編程中是常見的操做。也能夠稱爲「注入」和「摺疊」,例舉說明它是如何工做的。

var a = [1, 2, 3, 4, 5];
var sum = a.reduce(function(x, y) {return x + y}, 0); //數組求和
var produce = a.reduce(function(x, y) {return x * y}, 1); //數組求積
var max = a.reduce(function(x,y){return (x>y)?x:y;}); //求最大值

reduce()須要兩個參數,第一個是執行化簡操做的函數。化簡函數的任務就是用某種方法把兩個值組合成化簡一個值,並返回化簡後的值。在上述例子中,函數經過加法,乘法或最大值法合成兩個值。第二個(可選)的參數是第一個傳遞給函數的初始值。

reduce()使用的函數與forEach()和map()使用的函數不一樣。比較熟悉的是,數組元素、元素的索引和數組自己做爲第2-4個參數傳遞給函數。第一個參數是到目前爲止的化簡操做累計的結果。第一次操做函數時,第一個參數是一個初始值,它就是傳遞給reduce()的第二個參數。在接下來的調用中,這個值就是上一次化簡函數的返回值。在上面的第一個例子中,第一次調用化簡函數時的參數是0和1。這二者相加並返回1.在此調用的時的參數是1和2,它返回3.而後計算3+3=6,6+4=10,最後計算10+5=15.最後的值是15.reduce()返回這個值。

可能已經注意到了,上面三次調用reduce()時只有一個參數:沒有指定初始值。當不指定初始值調用reduce()時,它將使用數組的第一個元素做爲其初始值。這意味着第一次調用化簡函數就使用了第一個和第二個數組元素做爲其第一個和第二個參數。在上面求和和求積的例子中,能夠省略初始值參數。

在空數組上,不帶初始值參數調用reduce()致使類型錯誤異常。若是調用它的時候只有一個值--數組只有一個元素而且沒有指定初始值,或者有一個空數組而且指定一個初始值--reduce()只是簡單地返回那個值而不會調用化簡函數。

reduceRight()工做原理和reduce()同樣,不一樣的是它按照數組索引從高到低,(從右至左)處理數組,而不是從低到高。若是簡化操做的優先順序是從右到左,你可能想使用它,例如:

var a = [2, 3, 4];
 //計算2^(3^4)。乘方操做的優先順序是從右到左
var big = a.reduceRight(function(accumlator, value) {
    return Math.pow(value, accumlator);
});

注意:reduce()和reduceRight()都能接受一個可選參數,它指定了化簡函數調用的this關鍵字的值。可選的初始值參數仍然須要佔一個位置。若是想要化簡函數做爲一個特殊對象的方法調用,請參看function.blind()方法。

值得注意的是,上面描述的every()和some()方法是一種類型的數組化操做。可是不一樣的是,他們會盡早終止遍歷而不老是訪問每個數組元素。

爲了簡單起見,到目前位置所展現的例子都是數值的,但數學計算不是reduce()和reduceRight()的惟一意圖。考慮下6.2中的union()函數,它計算兩個對象的並集,並返回另外一個新對象,因此它的工做原理和一個簡化函數同樣,而且可使用reduce()來把它通常化,計算任意數目對象的「並集」。

var objects =[{x:1},{y:2},{z:3}];
var merged = objects.reduce(union); //=>{x:1,y:2,z:3}

回想一下,當兩個對象擁有同名的屬性時,union()函數使用第一個參數的屬性值,這樣,reduce()和reduceRighet()在師院union()時給出了不一樣的結果:

var objects =[{x:1,a:1},{y:2,a:2},{z:3,a:3}]; 
var leftunion = objects.reduce(union); //=>{x: 1, a: 3, y: 2, z: 3}
var rightunion = objects.reduceRight(union); //=> {z: 3, a: 1, y: 2, x: 1}

9.6.indexOf()和lastIndexOf()

indexOf()和lastIndexOf()搜着這個數組時具備給定值的元素,返回找到的第一個元素的全部或者沒有找到就返回-1.indexOf()從頭到尾搜索,而lastIndexOf()則反方向搜索。

a = [0, 1, 2, 1, 0];
a.indexOf(1); //=>1:a[1]是1
a.lastIndexOf(1); //=>3:a[3]是1
a.indexOf(3) //=>-1:沒有找到值爲3的元素

對於本節描述的其它方法,indexOf()和lastIndexOf()方法不接受一個函數做爲其參數,第一個參數須要搜索的值,第二個參數是可選的:它指定數組中的一個索引,從哪裏開始搜索。若是省略該參數,indexOf()從頭開始搜索,而lastIndexOf()從末尾開始搜索。第二個參數也能夠是負數,它表示相對數組末尾的偏移量。

以下函數在一個數組中搜索指定的值並返回包含全部匹配的數組索引的一個數組,它展現瞭如何運用 indexOf的第二個參數來查找除了第一個之外匹配的值。

//在數組中查找全部出現的x,並返回一個包含匹配索引的數組
function findall(a, x) {
    var results = [], //將會返回的數組
        len = a.length, //待搜索數組的長度
        pos = 0; //開始搜索的位置
    while (pos < len) { //循環搜素多個元素...
        pos = a.indexOf(x, pos); //搜素
        if (pos === -1) break; //未找到,就完成搜素
        results.push(pos); //不然,在數組中存儲索引
        pos = pos + 1; //並從下一個位置開始搜索
    }
    return results; //返回包含索引的數組
};

console.log(findall([1,2,5,3,4,5,6,7,5], 5))   //[2,5,8]

注意,字符串也有indexOf()和lastIndexOf()方法,它們和數組方法功能相似。

10.數組類型

數組是具備特殊行爲的對象。給定一個未知的對象,斷定它是否爲數組一般很是有用,在ECMAScript5中,可使用Array.isArray()函數來作這件事情

Array.isArray([]); //true
Array.isArray({}); //false

可是在ECMAScript5之前,要區分數組和非數組對象卻使人驚訝的困難。typeof()操做符在這裏幫不上忙:對數組它只返回:object(而且對除了函數之外的對象都是如此)。instanceof操做符只能用於簡單情形:

[] instanceof Array; //=>true
({}) instanceof Array; //=>false

使用instanceof的問題是在web瀏覽器中有可能有多個窗口或窗體(frame)存在。每一個窗口都有本身的JavaScript環境,有本身的全局對象。而且,每一個全局對象有本身的一組構造函數。所以一個窗體中的對象將不多是另外窗體中的構造函數的實例。窗體之間的混淆不常發生,但這個問題足以證實instanceof操做符不能視爲一個可靠的數組檢測方法。

解決方案是檢查對象的類屬性,對數組而言改屬性的值老是「Array」,所以在ECMAScript3中isArray()函數代碼能夠這樣寫

var isArray = Function.isArray || function(o) {
    return typeof o === "object" && Object.prototype.toString.call(o) === "[object Array]";
};

11.類數組對象

咱們已經看到,javascript數組的有一些特性是其它對象所沒有的:

  • 當有新的元素添加到列表時,自動更新length屬性
  • 設置length爲一個較小的值將截斷數組
  • 從Array.prototype中繼承一些有用的方法。
  • 其類屬性爲"Array"

這些特性讓javascript數組和常規的對象有明顯的區別,可是他們不是定義數組的本質特性。一種經常徹底合理的見解把擁有一個數值length屬性和對於非負整數屬性的對象看作一種類型的數組。

實踐中這些「類數組」對象實際上偶爾出現,雖然不能再他們上直接調用數組的方法或者指望length屬性有什麼特殊行爲,可是仍然能夠用針對真正數組遍歷的代碼來實現遍歷它們,結論就是不少數組算法徵對類數組對象工做得很好,就像徵對真正的數組同樣。若是算法把數組當作只讀的或者若是它們至少保持數組長度不變,也尤爲是這種狀況。如下代碼爲一個常規對象增長了一些屬性使其變成類數組對象,而後遍歷生成僞數組「元素」:

var a = {}; //從一個常規空對象開始
 //添加一組屬性,稱爲「類數組」
var i = 0;
while (i < 10) {
    a[i] = i * i;
    i++;
}
a.length = i;
console.log(a) //如今當真正的數組遍歷它
var total = 0;
for (var j = 0; j < a.length; j++)
    total += a[j];
console.log(total)

Arguments對象就是一個類數組的對象。在客戶端javascript中,一些DOM方法(如document.getElementsByTagName())也返回類數組對象。下面有一個函數 能夠用來檢測類數組對象

 //斷定o是不是一個類數組對象
 //字符串和函數都length屬性,可是他們能夠有typeOf檢測將其排除
 //在客戶端javascript中,DOM文本節點也有length屬性,須要用額外的o.nodetype != 3將其排除
function isArrayLike(o) {
    if (o && //o非null、undefined等
        typeof o === "object" && //o是對象
        isFinite(o.length) && //o.length是有限數
        o.length >= o && //o.length是非負數
        o.length === Math.floor(o.length) && //o.length是整數
        o.length < 4294967296) //o.length < 2^32
        return true;
    else
        return fasle; //不然它不是
}

javascript數組方法是特地定義爲通用的,所以他們不只應用在正真的數組,並且在類數組對象上都能正常工做。在ECMAScript5中,全部數組方法都是通用的。在ECMAScript3中,除了toString()和toLocaleString覺得全部的方法也是通用的(concat()方法是一個特例:雖然能夠用在相似的數組對象上,但它沒有將那個對象擴充進返回的數組中)。既然類數組沒有繼承Array.prototype,那就不能再它上面直接調用數組方法。儘管如此,能夠間接的使用Function.call方法調用:

var a ={"0":"a","1":"b","2":"c",length:3}
Array.prototype.join.call(a,"+") //=>"a+b+c"
Array.prototype.slice.call(a,0) //=>["a","b","c"]:真正的數組副本
Array.prototype.map.call(a.Function(x){
  return x.toUpperCase();
}) //=>["A","B","C"] 

ECMAScript1.5數組方法是在Firefox 1.5中引入的。因爲它們寫法的通常性,Firefox還將這些方法的版本在Array構造函數上直接定義爲函數,使用這些方法定義的版本,上個例子能夠這麼寫:

var a = {"0": "a","1": "b","2": "c",length: 3};
Array.join(a, "+");
Array.slice(a, 0);
Array.map(a, function(x) {
    return x.toUpperCase();
})

用在類數組對象上時,數組方法的靜態函數版本方法很是有用。但既然它們不是標準的,不能指望它們在全部瀏覽器都有定義。能夠這樣書寫代碼保證它們以前是存在的

Array.join = Array.join || function(a, serp) {
    return Array.prototype.join.call(a, serp);
};
Array.slice = Array.slice || function(a, form, to) {
    return Array.prototype.slice.call(a, form, to);
};
Array.map = Array.map || function(a, f, thisArg) {
    return Array.prototype.map.call(a, f, thisArg);
};

12.做爲數組的字符串

在ECMAScript5(在衆多最新瀏覽器已經實現--包括ie8,遭遇ECMAScript5)中,字符串的行爲相似於只讀數組。除了用charAt()方法來訪問單個字符之外,還可使用方括號

var s = "test";
s.charAt(0); //=> "t"
s[0] ;  // =>"t"

固然徵對字符串的typeOf操做符仍然返回"String",可是若是給Array.isArray()傳遞字符串,它將返回false.

可索引字符串的最大好處就是簡單,用方括號代替了chartAt()調用,這樣更加簡潔、可讀性更高效。不只如此,字符串的行爲相似於數字的事實是的通用數組方法能夠應用到字符串上。例如:

var s = "javascript"
Array.prototype.join.call(s, " "); //=>' j a v a s c r i p t'
Array.prototype.filter.call(s, function(x) { //過濾字符串中的字符
        return x.match(/[^aeiou]/); //匹配非元音字符
    }).join("") //=>jvscrpt

請記住,字符串是不可變值,故當他們做爲數組看待時,他們是隻讀的。如push()、sort()、reverse()、splice()、等數組方法會修改數組,它們在字符串上是無效的。不只如此,使用數組方法修改字符串會致使錯誤,出錯的時候沒有提示。

相關文章
相關標籤/搜索