數組簡介(瞭解)
數組是值的有序集合。每個值叫做一個元素,而每個元素在數組中有一個位置,以數字表示,稱爲索引。
JavaScript數組是無類型的:數組元素可以是任意類型,並且同一個數組中的不同元素也可能有不同的類型。數組的元素甚至也可能是對象或其他數組,這允許創建複雜的數據結構,如對象的數組和數組的數組。
JavaScript數組的索引是基於零的32位數值:第一個元素的索引爲0,最大可能的索引爲4294967294(2^32-2),數組最大能容納4294967295個元素。
JavaScript數組是動態的:根據需要它們會增長或縮減,並且在創建數組時無須聲明一個固定的大小或者在數組大小變化時無須重新分配空間。
JavaScript數組可能是稀疏的:數組元素的索引不一定要連續的,它們之間可以有空缺。
每個JavaScript數組都有一個length屬性。針對非稀疏數組,該屬性就是數組元素的個數。針對稀疏數組,length比所有元素的索引要大。
創建數組(掌握)
數組直接量
使用數組直接量是創建數組最簡單的方法,在方括號中將數組元素用逗號隔開即可。
var empty=[];//沒有元素的數組
var primes=[2,3,5,7,11];//有5個數值的數組
var misc=[1.1,true,"a",];//3個不同類型的元素和結尾的逗號
如果省略數組直接量中的某個值,省略的元素將被賦子予undefined值:
var count=[1,,3];//數組有3個元素,中間的那個元素值爲undefined
var undefs=[,,];//數組有2個元素,都是undefined
數組直接量的語法允許有可選的結尾的逗號,故[,,]只有兩個元素而非三個。
調用構造函數Array()
- 調用時沒有參數:
var a=new Array();
該方法創建一個沒有任何元素的空數組,等同於數組直接量[]。
- 調用時有一個數值參數,它指定長度:
var a=new Array(10);
該技術創建指定長度的數組。當預先知道所需元素個數時,這種形式的Array()構造函數可以用來預分配一個數組空間。注意,數組中沒有存儲值,甚至數組的索引屬性“0”、“1”等還未定義。
- 顯式指定兩個或多個數組元素或者數組的一個非數值元素:
var a =new Array(5,4,3,2,1,"testing,testing");
以這種形式,構造函數的參數將會成爲新數組的元素。使用數組字面量比這樣使用Array()構造函數要簡單多了。
數組元素的讀和寫(掌握)
使用[]操作符來訪問數組中的一個元素。數組的引用位於方括號的左邊。方括號中是一個返回非負整數值的任意表達式。使用該語法既可以讀又可以寫數組的一個元素。
var a=["world"];//從一個元素的數組開始
var value=a[o];//讀第0個元素
a[1]=3.14;//寫第1個元素
i=2;
a[i]=3;//寫第2個元素
a[i+1]="hello";//寫第3個元素
a[a[i]]=a[o];//讀第0個和第2個元素,寫第3個元素
所有的索引都是屬性名,但只有在0~2^32-2之間的整數屬性名纔是索引。所有的數組都是對象,可以爲其創建任意名字的屬性。但如果使用的屬性是數組的索引,數組的特殊行爲就是將根據需要更新它們的length屬性值。
注意,可以使用負數或非整數來索引數組。這種情況下,數值轉換爲字符串,字符串作爲屬性名來用。如果湊巧使用了是非負整數的字符串,它就當做數組索引,而非對象屬性,當使用的一個浮點數和一個整數相等時情況也是一樣的:
a[-1.23]=true;//這將創建一個名爲“-1.23"的屬性
a["1000"]=0;//這是數組的第1001個元素
a[1.000]//和a[1]相等
既然數組是對象,那麼它們可以從原型中繼承元素。在ECMAScript5中,數組可以定義元素的getter和setter方法。如果一個數組確實繼承了元素或使用了元素的getter和setter方法,你應該期望它使用非優化的代碼路徑:訪問這種數組的元素的時間會與常規對象屬性的查找時間相近。
稀疏數組(掌握)
稀疏數組就是包含從0開始的不連續索引的數組。通常,數組的length屬性值代表數組中元素的個數。如果數組是稀疏的,length屬性值大於元素的個數。可以用Array()構造函數或簡單地指定數組的索引值大於當前的數組長度來創建稀疏數組。
a=new Array(5);//數組沒有元素,但是a.length是5
a=[];//創建一個空數組,length=o
a[1000]=0;//賦值添加一個元素,但是設置1ength爲1001
注意,當在數組直接量中省略值時不會創建稀疏數組。省略的元素在數組中是存在的,其值爲undefined。這和數組元素根本不存在是有一些微妙的區別的。可以用in操作符檢測兩者之間的區別:
var al=[,,,];//數組是[undefined,undefined,undefined]
var a2=new Array(3);//該數組根本沒有元素
0 in al//=>true:a1在索引0處有一個元素
0 in a2//=>false:a2在索引0處沒有元素
需要注意的是,當省略數組直接量中的值時(使用連續的逗號,比如[1,,3]),這時所得到的數組也是稀疏數組,省略掉的值是不存在的:
var al=[,];//此數組沒有元素,長度是1
var a2=[undefined];//此數組包含一個值爲undefined的元素
0 in a1//=>false:al在索引0處沒有元素
0 in a2//=>true:a2在索引0處有一個值爲undefined的元素
數組長度(掌握)
每個數組有一個length屬性,就是這個屬性使其區別於常規的JavaScript對象。針對稠密(也就是非稀疏)數組,1ength屬性值代表數組中元素的個數。其值比數組中最大的索引大1:
[].length//=>0:數組沒有元素
[a','b','c'].length//=>3:最大的索引爲2,length爲3
當數組是稀疏的時,1ength屬性值大於元素的個數。而且數組長度保證大於它每個元素的索引值。或者,換一種說法,在數組中(無論稀疏與否)肯定找不到一個元素的索引值大於或等於它的長度。
爲了維持此規則不變化,數組有兩個特殊的行爲。第一個如同上面的描述:如果爲一個數組元素賦值,它的索引i大於或等於現有數組的長度時,length屬性的值將設置爲i+1。
第二個特殊的行爲就是設置length屬性爲一個小於當前長度的非負整數n時,當前數組中那些索引值大於或等於n的元素將從中刪除:
a=[1,2,3,4,5];//從5個元素的數組開始
a.length=3;//現在a爲[1,2,3]
a.length=0;//刪除所有的元素。a爲[]
a.length=5;//長度爲5,但是沒有元素,就像new Array(5)
還可以將數組的length屬性值設置爲大於其當前的長度。實際上這不會向數組中添加新的元素,它只是在數組尾部創建一個空的區域。
在ECMAScript5中,可以用Object.defineProperty()讓數組的length屬性變成只讀的
a=[1,2,3];//從3個元素的數組開始
Object.defineProperty(a,“1ength",
{writable:false});//讓length屬性只讀
a.length=0;//a不會改變
類似地,如果讓一個數組元素不能配置,就不能刪除它。如果不能刪除它,length屬性不能設置爲小於不可配置元素的索引值。
數組元素的添加和刪除(掌握)
添加
爲新索引賦值:
a=[]//開始是一個空數組
a[o]="zero";//然後向其中添加元素
a[1]="one";
也可以使用push()方法在數組末尾增加一個或多個元素:
a=[];//開始是一個空數組
a.push("zero")//在末尾添加一個元素。a=["zero"]
a.push("one","two")//再添加兩個元素。a=["zero","one","two"]
在數組尾部壓入一個元素與給數組a[a.length]賦值是一樣的。可以使用unshift()方法在數組的首部插入一個元素,並且將其他元素依次移到更高的索引處。
刪除
可以像刪除對象屬性一樣使用delete運算符來刪除數組元素:
a=[1,2,3];
delete a[1];//a在索引1的位置不再有元素
1 in a//=>false:數組索引1並未在數組中定義
a.length//=>3:delete操作並不影響數組長度
刪除數組元素與爲其賦undefined值是類似的(但有一些微妙的區別)。注意,對一個數組元素使用delete不會修改數組的length屬性,也不會將元素從高索引處移下來填充已刪除屬性留下的空白。如果從數組中刪除一個元素,它就變成稀疏數組。
上面我們看到,也可以簡單地設置length屬性爲一個新的期望長度來刪除數組尾部的元素。
數組有pop()方法,使減少長度1並返回被刪除元素的值。還有一個shift()方法,從數組頭部刪除一個元素。和delete不同的是shift()方法將所有元素下移到比當前索引低1的地方。
最後,splice()是一個通用的方法來插入、刪除或替換數組元素。它會根據需要修改length屬性並移動元素到更高或較低的索引處。
數組遍歷(掌握)
在嵌套循環或其他性能非常重要的上下文中,可以看到這種基本的數組遍歷需要優化,數組的長度應該只查詢一次而非每次循環都要查詢:
for(var i=o,len=keys.length;i<len;i++){
//循環體仍然不變
}
這些例子假設數組是稠密的,並且所有的元素都是合法數據。否則,使用數組元素之前應該先檢測它們。如果想要排除nu11、undefined和不存在的元素,代碼如下:
for(var i=o;i<a.length;i++){
if(!a[i])continue;//跳過null、undefined和不存在的元素
//循環體
}
如果只想跳過undefined和不存的元素,代碼如下:
for(var i=0;i<a.length;i++){
if(a[i]===undefined)continue;//跳過undefined+不存在的元素
//循環體
}
最後,如果只想跳過不存在的元素而仍然要處理存在的undefined元素,代碼如下:
for(var i=o;i< a.length;i++){
if(!(i in a))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;//平方相加
});
SumOf5quares //=)55:1+4+9+16+25
多維數組(掌握)
JavaScript不支持真正的多維數組,但可以用數組的數組來模似。訪問數組的數組中的元素,只要簡單地使用兩次[]操作符即可。這裏有一個具體的例子,它使用二維數組作爲一個九九乘法表:
//創建一個多維數組
var table=new Array(10);//表格有10行
for(vari=o;i<table.length;i++){
table[i]=new Array(10);//每行有10列
}
//初始化數組
for(var row=0;row< table.length;row++){
for(col=o;col<table[row].length;col++){
table[row][col]=row*col;
}
}
//使用多維數組來計算(查詢)5*7
var product=table[5][7];//35
數組方法(掌握)
ECMAScript 3
join()轉化爲字符串
Array.join()方法將數組中所有元素都轉化爲字符串並連接在一起,返回最後生成的字符串。可以指定一個可選的字符串在生成的字符串中來分隔數組的各個元素。如果不指定分隔符,默認使用逗號。如以下代碼所示:
var a=[1,2,3];//創建一個包含三個元素的數組
a.join();//=>"1,2,3"
a.join("");//=>"123"
a.join("");//=>"123"
var b=new Array(10);//長度爲10的空數組
b.join('-')//=-:9個連字號組成的字符串
Array.join()方法是String.split()方法的逆向操作,後者是將字符串分割成若干塊來創建一個數組。
reverse()顛倒順序
Array.reverse()方法將數組中的元素顛倒順序,返回逆序的數組。它採取了替換;換句話說,它不通過重新排列的元素創建新的數組,而是在原先的數組中重新排列它們。
var a=[1,2,3];
a.reverse().join()//=>"3,2,1",並且現在的a是[3,2,1]
sort()元素排序
Array.sort()方法將數組中的元素排序並返回排序後的數組。當不帶參數調用sort()時,數組元素以字母表順序排序(如有必要將臨時轉化爲字符串進行比較):
var a=new Array("banana","cherry","apple");
a. sort();
var s=a. join(",");//s=="apple, banana, cherry"
如果數組包含undefined元素,它們會被排到數組的尾部。
爲了按照其他方式而非字母表順序進行數組排序,必須給sort()方法傳遞一個比較函數。該函數決定了它的兩個參數在排好序的數組中的先後順序。假設第一個參數應該在前,比較函數應該返回一個小於0的數值。反之,假設第一個參數應該在後,函數應該返回一個大於0的數值。並且,假設兩個值相等(也就是說,它們的順序無關緊要),函數應該返回0。因此,例如,用數值大小而非字母表順序進行數組排序,代碼如下:
var a=[33,4,1111,222];
a.sort();//字母表順序:1111,222,33,4
a.sort(function(a,b){//數值順序:4,33,222,1111
return a-b;//根據順序,返回負數、0、正數
});
a.sort(function(a,b){return b-a});//數值大小降序
另外一個數組元素排序的例子,也許需要對一個字符串數組執行不區分大小寫的字母表排序,比較函數首先將參數都轉化爲小寫字符串(使用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 o;
});//=>I'ant',Bug','cat',0og']
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]]
slice()截取指定元素
Array.slice()方法返回指定數組的一個片段或子數組。它的兩個參數分別指定了片段的開始和結束的位置。返回的數組包含第一個參數指定的位置和所有到但不含第二個參數指定的位置之間的所有數組元素。
如果只指定一個參數,返回的數組將包含從開始位置到數組結尾的所有元素。如參數中出現負數,它表示相對於數組中最後一個元素的位置。例如,參數-1指定了最後一個元素,而-3指定了倒數第三個元素。注意,slice()不會修改調用的數組。下面有一些示例:
var a=[1,2,3,4,5];
a.slice(o,3);//返回[1,2,3]
a.slice(3);//返回[4,5]
a.slice(1,-1);//返回[2,3,4]
a.slice(-3,-2);//返回[3]
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()會插入數組本身而非數組的元素。
push()和pop()數組尾部的插入或刪除
push()和pop()方法允許將數組當做棧來使用。push()方法在數組的尾部添加一個或多個元素,並返回數組新的長度。
pop()方法則相反:它刪除數組的最後一個元素,減小數組長度並返回它刪除的值。注意,兩個方法都修改並替換原始數組而非生成一個修改版的新數組。組合使用push()和pop()能夠用JavaScript數組實現先進後出的棧。例如:
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();//stack:[]返回1
unshift()和shift()數組頭部的插入或刪除
unshift()在數組的頭部添加一個或多個元素,並將已存在的元素移動到更高索引的位置來獲得足夠的空間,最後返回數組新的長度。
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()方法)而非一次一個地插入。這意味着最終的數組中插入的元素的順序和它們在參數列表中的順序一致。而假如元素是一次一個地插入,它們的順序應該是反過來的。
toString()和toLocaleString()
數組和其他JavaScript對象一樣擁有toString()方法。針對數組,該方法將其每個元素轉化爲字符串(如有必要將調用元素的toString()方法)並且輸出用逗號分隔的字符串列表。注意,輸出不包括方括號或其他任何形式的包裹數組值的分隔符。例如:
[1,2,3].toString()//生成‘1,2,3’
["a","b","c"].toString()//生成‘a,b,c'
[1,[2,'c']].tostring()//生成’1,2,c'
注意,這裏與不使用任何參數調用join()方法返回的字符串是一樣的。
toLocaleString()是toString()方法的本地化版本。它調用元素的toLocaleString()方法將每個數組元素轉化爲字符串,並且使用本地化(和自定義實現的)分隔符將這些字符串連接起來生成最終的字符串。
ECMAScript 5
在開始詳細介紹之前,很有必要對ECMAScript5中的數組方法做一個概述。首先,大多數方法的第一個參數接收一個函數,並且對數組的每個元素(或一些元素)調用一次該函數。如果是稀疏數組,對不存在的元素不調用傳遞的函數。
在大多數情況下,調用提供的函數使用三個參數:數組元素、元素的索引和數組本身。通常,只需要第一個參數值,可以忽略後兩個參數。
大多數ECMAScript5數組方法的第一個參數是一個函數,第二個參數是可選的。如果有第二個參數,則調用的函數被看做是第二個參數的方法。也就是說,在調用函數時傳遞進去的第二個參數作爲它的this關鍵字的值來使用。被調用的函數的返回值非常重要,但是不同的方法處理返回值的方式也不一樣。
ECMAScript5中的數組方法都不會修改它們調用的原始數組。當然,傳遞給這些方法的函數是可以修改這些數組的。
forEach()數組遍歷
forEach()方法從頭至尾遍歷數組,爲每個元素調用指定的函數。如上所述,傳遞的函數作爲forEach()的第一個參數。然後forEach()使用三個參數調用該函數:數組元素、元素的索引和數組本身。如果只關心數組元素的值,可以編寫只有一個參數的函數—一額外的參數將忽略:
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");
map()遍歷並返回新數組
map()方法將調用的數組的每個元素傳遞給指定的函數,並返回一個數組,它包含該函數的返回值,例加。
a=[1,2,3];
b=a.map(function(x){return x*x;});//b是[1,4,9]
傳遞給map()的函數的調用方式和傳遞給forEach()的函數的調用方式一樣。但傳遞給map()的函數應該有返回值。
注意,map()返回的是新數組:它不修改調用的數組。如果是稀疏數組,返回的也是相同方式的稀疏數組:它具有相同的長度,相同的缺失元素。
filter()遍歷並返回運算爲true的元素數組
fliter()方法返回的數組元素是調用的數組的一個子集。傳遞的函數是用來邏輯判定的:該函數返回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 8& x!=null;});
every()和some()遍歷並判斷元素返回true或false
every()和some()方法是數組的邏輯判定:它們對數組元素應用指定的函數進行判定,返回true或false。
every()方法就像數學中的“針對所有”的量詞V:當且僅當針對數組中的所有元素調用判定函數都返回true,它才返回true:
a=[1,2,3,4,5];
a.every(function(x){return x<10;})//=>true:所有的值<10
a.every(function(x){ returnx%2===0;})//=>false:不是所有的值都是偶數
some()方法就像數學中的“存在”的量詞:當數組中至少有一個元素調用判定函數返回true,它就返回true;並且當且僅當數值中的所有元素調用判定函數都返回false,它才返回false:
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。
reduce()和reduceRight()遍歷元素進行組合並生成單個值
reduce()
reduce()和reduceRight()方法使用指定的函數將數組元素進行組合,生成單個值。這在函數式編程中是常見的操作,也可以稱爲“注入”和“摺疊”。舉例說明它是如何工作的:
var a=[1,2,3,4,5]
var sum=a.reduce(function(x,y){ return x+y},0);//數組求和
var product=a.reduce(function(x,y){return x*y},1);//數組求積
var max=a.reduce(function(x,y){ return(x>y)?x:y;});//求最大值
reduce()需要兩個參數。第一個是執行化簡操作的函數。化簡函數的任務就是用某種方法把兩個值組合或化簡爲一個值,並返回化簡後的值。在上述例子中,函數通過加法、乘法或取最大值的方法組合兩個值。第二個(可選)的參數是一個傳遞給函數的初始值。
可能已經注意到了,上面第三次調用reduce()時只有一個參數:沒有指定初始值。當不指定初始值調用reduce()時,它將使用數組的第一個元素作爲其初始值。這意味着第一次調用化簡函數就使用了第一個和第二個數組元素作爲其第一個和第二個參數。在上面求和與求積的例子中,可以省略初始值參數。
reduce()使用的函數與forEach()和map()使用的函數不同。比較熟悉的是,數組元素、元素的索引和數組本身將作爲第2~4個參數傳遞給函數。第一個參數是到目前爲止的化簡操作累積的結果。
第一次調用函數時,第一個參數是一個初始值,它就是傳遞給reduce()的第二個參數。在接下來的調用中,這個值就是上一次化簡函數的返回值。
在空數組上,不帶初始值參數調用reduce()將導致類型錯誤異常。如果調用它的時候只有一個值——數組只有一個元素並且沒有指定初始值,或者有一個空數組並且指定一個初始值—reduce()只是簡單地返回那個值而不會調用化簡函數。
reduceRight()
reduceRight()的工作原理和reduce()一樣,不同的是它按照數組索引從高到低(從右到左)處理數組,而不是從低到高。如果化簡操作的優先順序是從右到左,你可能想使用它,例如:
var a=[2,3,4]
//計算2 ^(3^4)。乘方操作的優先順序是從右到左
var big=a.reduceRight(function(accumulator,value){
return Math.pow(value,accumulator);
});
注意,reduce()和reduceRight()都能接收一個可選的參數,它指定了化簡函數調用時的this關鍵字的值。可選的初始值參數仍然需要佔一個位置。如果想讓化簡函數作爲一個特殊對象的方法調用,請參看Function.bind()方法。
爲了簡單起見,到目前位置所展示的例子都是數值的,但數學計算不是reduce()和reduceRight()的唯一意圖。
indexOf()和lastindexOf()搜索數組返回索引
indexOf()和lastIndexOf()搜索整個數組中具有給定值的元素,返回找到的第一個元素的索引或者如果沒有找到就返回-1。indexOf()從頭至尾搜索,而lastIndexOf()則反向搜索。
a=[o,1,2,1,0];
a.indexOf(1)//=>1:a[1]是1
a.lastIndexOf(1)//=>3:a[3]是1
a.indexOf(3)//=>-1:沒有值爲3的元素
不同於本節描述的其他方法,indexOf()和1astIndexOf()方法不接收一個函數作爲其參數。第一個參數是需要搜索的值,第二個參數是可選的:它指定數組中的一個索引,從那裏開始搜索。如果省略該參數,indexOf()從頭開始搜索,而lastIndexOf()從末尾開始搜索。第二個參數也可以是負數,它代表相對數組末尾的偏移量,例如,-1指定數組的最後一個元素。
如下函數在一個數組中搜索指定的值並返回包含所有匹配的數組索引的一個數組。它展示瞭如何運用indexof()的第二個參數來查找除了第一個以外匹配的值。
//在數組中查找所有出現的x,並返回一個包含匹配索引的數組
function findall(a,x){
var results=[],//將會返回的數組
len=a.length,//待搜索數組的長度
pos=0;//開始搜索的位置
while(pos<len){//循環搜索多個元素..
pos=a.indexoOf(x,pos);//搜索
if(pos===-1)break;//未找到,就完成搜索
results.push(pos);//否則,在數組中存儲索引
pos=pos+1;//並從下一個位置開始搜索
return results;//返回包含索引的數組
}
}
數組類型(掌握)
我們在本章中到處都可以看見數組是具有特殊行爲的對象。給定一個未知的對象,判定它是否爲數組通常非常有用。在ECMAScript5中,可以使用Array.isArray()函數來做這件事情:
Array.isArray([])//=>true
Array.isArray({})//=>false
instanceof操作符只能用於簡單的情形:使用instanceof的問題是在Web瀏覽器中有可能有多個窗口或窗體(frame)存在。每個窗口都有自己的JavaScript環境,有自己的全局對象。並且,每個全局對象有自己的一組構造函數。因此一個窗體中的對象將不可能是另外窗體中的構造函數的實例。窗體之間的混淆不常發生,但這個問題足已證明instanceof操作符不能視爲一個可靠的數組檢測方法。
[]instanceof Array //=>true
({})instanceof Array //=>false
解決方案是檢查對象的類屬性。對數組而言該屬性的值總是“Array”,因此在ECMAScript3中isArray()函數的代碼可以這樣書寫:
var isArray=Function.isArray 1| function(o){
return typeof o==="object"8&
Object.prototype.tostring.call(o)==="[Object Array]";
};
實際上,此處類屬性的檢測就是ECMAScript5中Array.isArray()函數所做的事情。獲得對象類屬性的技術使用了Object.prototype.toString()方法。
類數組對象(掌握)
一種常常完全合理的看法把擁有一個數值length屬性和對應非負整數屬性的對象看做一種類型的數組。實踐中這些“類數組”對象實際上偶爾出現,雖然不能在它們之上直接調用數組方法或者期望length屬性有什麼特殊的行爲,但是仍然可以用針對真正數組遍歷的代碼來遍歷它們。結論就是很多數組算法針對類數組對象工作得很好,就像針對真正的數組一樣。如果算法把數組看成只讀的或者如果它們至少保持數組長度不變,也尤其是這種情況。
以下代碼爲一個常規對象增加了一些屬性使其變成類數組對象,然後遍歷生成的僞數組的“元素”:
var a={};//從一個常規空對象開始
//添加一些屬性,稱爲“類數組"
var i=0;
while(i<10){
a[i]=i*i;
i++;
}
a.length=i;
//現在,當做真正的數組遍歷它
var total=0;
for(varj=0;j<a.length;j++)
total+=a[j];
Arguments對象就是一個類數組對象。在客戶端JavaScript中,一些DOM方法(如document.getElementsByTagName())也返回類數組對象。下面有一個函數可以用來檢測類數組對象:
//判定。是否是一個類數組對象
//字符串和函數有1ength屬性,但是它們
//可以用typeof檢測將其排除。在客戶端Java5cript中,DOM文本節點
//也有1ength屬性,需要用額外判斷o.nodeType!=3將其排除
function isArraylike(o){
if(o8&//o非null、undefined等
typeof o==="object"8&//o是對象isFinite(o.length)8&//o.1ength是有限數值
o.1ength>=0&&//o.length爲非負值
o.1ength===Math.floor(o.1ength)&&//o.1ength是整數
o.length<4294967296)//o.length<232
return true;//o是類數組對象
else
return false;//否則它不是
}
類似上述的類數組對象的檢測方法針對字符串常常返回false——它們通常最好當做字符串處理,而非數組。
JavaScript數組方法是特意定義爲通用的,因此它們不僅應用在真正的數組而且在類數組對象上都能正確工作。在ECMAScript5中,所有的數組方法都是通用的。在ECMAScript3中,除了toString()和tolocaleString()以外的所有方法也是通用的。(concat()方法是一個特例:雖然可以用在類數組對象上,但它沒有將那個對象擴充進返回的數組中。)既然類數組對象沒有繼承自Array.prototype,那就不能在它們上面直接調用數組方法。儘管如此,可以間接地使用Function.cal1方法調用:
var a={"o":"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"]:
ECMAScript 5數組方法是在Firefox1.5中引入的。由於它們的寫法的一般性,Firefox還將這些方法的版本在Array構造函數上直接定義爲函數。使用這些方法定義的版本,上述例子就可以這樣重寫:
var a={"o":"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, sep){
return Array.prototype.join.call(a, sep);
};
Array. slice =Array.slice || function(a, from, to){
return Array.prototype.slice.call(a, from, to);
};
Array. map=Array.map || function(a,f, thisArg){
return Array.prototype.map.call(a,f, thisArg);
};
作爲數組的字符串(掌握)
在ECMAScript5(在衆多最近的瀏覽器實現——包括IE8——早於ECMAScript 5)中,字符串的行爲類似於只讀的數組。除了用charAt()方法來訪問單個的字符以外,還可以使用方括號:
var s="test";
s.charAt(o)//=>"t"
s[1]//=>"e"
可索引的字符串的最大的好處就是簡單,用方括號代替了charAt()調用,這樣更加簡潔、可讀並且可能更高效。不僅如此,字符串的行爲類似於數組的事實使得通用的數組方法可以應用到字符串上。例如:
s="JavaScript"
Array.prototype.join.call(s,"")//=)"JavaScript"
Array.prototype.filter.call(s,//過濾字符串中的字符
function(x){
return x.match(/[^aeiou]/);//只匹配非元音字母
}).join("")//=)"jvScrpt"
請記住,字符串是不可變值,故當把它們作爲數組看待時,它們是隻讀的。如push()、sort()、reverse()和splice()等數組方法會修改數組,它們在字符串上是無效的。不僅如此,使用數組方法來修改字符串會導致錯誤:出錯的時候沒有提示。