在JavaScript裡,數組是可修改的對象,這意味著創建的每個數組都有一些可用的方法。數組很有趣,因為它們十分強大,並且相比其他語言中的數組,JavaScript中的數組有許多很好用的方法。這樣就不用再為它開發一些基本功能了,例如在數據結構的中間添加或刪除元素。
下面的表格中詳述了數組的一些核心方法,其中的一些我們已經學習過了。
方法名
描述
concat
連接2個或更多數組,並返回結果
every
對數組中的每一項運行給定函數,如果該函數對每一項都返回true
,則返回true
filter
對數組中的每一項運行給定函數,返回該函數會返回true
的項組成的數組
forEach
對數組中的每一項運行給定函數。這個方法沒有返回值
join
將所有的數組元素連接成一個字符串
indexOf
返回第一個與給定參數相等的數組元素的索引,沒有找到則返回
1
lastIndexOf
返回在數組中搜索到的與給定參數相等的元素的索引裡最大的值
map
對數組中的每一項運行給定函數,返回每次函數調用的結果組成的數組
reverse
顛倒數組中元素的順序,原先第一個元素現在變成最後一個,同樣原先的最後一個元素變成了現在的第一個
slice
傳入索引值,將數組裡對應索引範圍內的元素作為新數組返回
some
對數組中的每一項運行給定函數,如果任一項返回true
,則返回true
sort
按照字母順序對數組排序,支持傳入指定排序方法的函數作為參數
toString
將數組作為字符串返回
valueOf
和toString
類似,將數組作為字符串返回
我們已經學過了push
、pop
、shift
、unshift
和splice
方法。下面來看表格中提到的方法。在本書接下來的章節裡,編寫數據結構和算法時會大量用到這些方法。
2.7.1 數組合併
考慮如下場景:有多個數組,需要合併起來成為一個數組。我們可以迭代各個數組,然後把每個元素加入最終的數組。幸運的是,JavaScript已經給我們提供了解決方法,叫作concat
方法:
var zero = 0;
var positiveNumbers = [1,2,3];
var negativeNumbers = [-3,-2,-1];
var numbers = negativeNumbers.concat(zero, positiveNumbers);
concat
方法可以向一個數組傳遞數組、對像或是元素。數組會按照該方法傳入的參數順序連接指定數組。在這個例子裡,zero
將被合併到nagativeNumbers
中,然後positiveNumbers
繼續被合併。最後輸出的結果是-3、-2、-1、0、1、2、3。
2.7.2 迭代器函數
有時我們需要迭代數組中的元素。前面我們已經學過,可以用循環語句來處理,例如for
語句。
JavaScript內置了許多數組可用的迭代方法。對於本節的例子,我們需要數組和函數。假如有一個數組,它值是從1到15,如果數組裡的元素可以被2整除(偶數),函數就返回true
,否則返回false
:
var isEven = function (x) {
// 如果x是2的倍數,就返回true
console.log(x);
return (x % 2 == 0) ? true : false;
};
var numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
return (x % 2 == 0) ? true : false
也可以寫成return (x % 2== 0)
。
用
every
方法迭代我們要嘗試的第一個方法是
every
。every
方法會迭代數組中的每個元素,直到返回false
。numbers.every(isEven);
在這個例子裡,數組
numbers
的第一個元素是1,它不是2的倍數(1是奇數), 因此isEven
函數返回false
,然後every
執行結束。用
some
方法迭代下一步,我們來看
some
方法。它和every
的行為類似,不過some
方法會迭代數組的每個元素,直到函數返回true
:numbers.some(isEven);
在我們的例子裡,
numbers
數組中第一個偶數是2(第二個元素)。第一個被迭代的元素是1,isEven
會返回false
。第二個被迭代的元素是2,isEven
返回true
——迭代結束。用
forEach
方法迭代如果要迭代整個數組,可以用
forEach
方法。它和使用for
循環的結果相同:numbers.forEach(function(x){ console.log((x % 2 == 0)); });
使用
map
和filter
方法JavaScript還有兩個會返回新數組的遍歷方法。第一個是
map
:var myMap = numbers.map(isEven);
數組
myMap
裡的值是:[false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]
。它保存了傳入map
方法的isEven
函數的運行結果。這樣就很容易知道一個元素是否是偶數。比如,myMap[0]
是false
,因為1不是偶數;而myMap[1]
是true
,因為2是偶數。還有一個
filter
方法。它返回的新數組由使函數返回true
的元素組成:var evenNumbers = numbers.filter(isEven);
在我們的例子裡,
evenNumbers
數組中的元素都是偶數:[2, 4, 6, 8, 10, 12, 14]
。使用
reduce
方法最後是
reduce
方法。reduce
方法接收一個函數作為參數,這個函數有四個參數:previousValue
、currentValue
、index
和array
。這個函數會返回一個將被疊加到累加器的值,reduce
方法停止執行後會返回這個累加器。如果要對一個數組中的所有元素求和,這就很有用,比如:numbers.reduce(function(previous, current, index){ return previous + current; });
輸出將會是120。
JavaScript的
Array
類還有另外兩個重要方法:map
和reduce
。這兩個方法名是自解釋的,這意味著map
方法會依照給定函數對值進行映射,而reduce
方法會依照函數規約數組包含的值。這三個方法(map
、filter
和reduce
)是我們要在第11章學習的JavaScript函數式編程的基礎。
2.7.3 ECMAScript 6和數組的新功能
第1章提到過,ECMAScript 6(ES6或ES2015)和ECMAScript 7(ES7或ES2016)規範給JavaScript語言帶來了新的功能。
下表列出了ES6和ES7新增的數組方法。
方法
描述
@@iterator
返回一個包含數組鍵值對的迭代器對象,可以通過同步調用得到數組元素的鍵值對
copyWithin
複製數組中一系列元素到同一數組指定的起始位置
entries
返回包含數組所有鍵值對的@@iterator
includes
如果數組中存在某個元素則返回true
,否則返回false
。ES7新增
find
根據回調函數給定的條件從數組中查找元素,如果找到則返回該元素
findIndex
根據回調函數給定的條件從數組中查找元素,如果找到則返回該元素在數組中的索引
fill
用靜態值填充數組
from
根據已有數組創建一個新數組
keys
返回包含數組所有索引的@@iterator
of
根據傳入的參數創建一個新數組
values
返回包含數組中所有值的@@iterator
除了這些新的方法,還有一種用for...of
循環來迭代數組的新做法,以及可以從數組實例得到的迭代器對象。
在後面的主題中,我們會演示所有的新功能。
使用
forEach
和箭頭函數迭代箭頭函數可以簡化使用
forEach
迭代數組元素的做法。代碼例子如下:numbers.forEach(function (x) { console.log(x % 2 == 0); });
這段代碼可以簡化如下:
numbers.forEach(x => { console.log((x % 2 == 0)); });
使用
for...of
循環迭代你已經學過用
for
循環和forEach
方法迭代數組。ES6還引入了迭代數組值的for...of
循環,來看看它的用法:for (let n of numbers) { console.log((n % 2 == 0) ? 'even' : 'odd'); }
可以訪問https://goo.gl/qHYAN1運行上面的示例。
使用ES6新的迭代器(
@@iterator
)ES6還為
Array
類增加了一個@@iterator
屬性,需要通過Symbol.iterator
來訪問。代碼如下:let iterator = numbers[Symbol.iterator]; console.log(iterator.next.value); // 1 console.log(iterator.next.value); // 2 console.log(iterator.next.value); // 3 console.log(iterator.next.value); // 4 console.log(iterator.next.value); // 5
然後,不斷調用迭代器的
next
方法,就能依次得到數組中的值。numbers
數組中有15個值,因此需要調用15次iterator.next.value
。數組中所有值都迭代完之後,
iterator.next.value
會返回undefined
。以上代碼的輸出和我們接下來要講的
numbers.value
是一樣的。訪問https://goo.gl/L81UQW查看和運行示例。
數組的
entries
、keys
和values
方法ES6還增加了三種從數組中得到迭代器的方法。我們首先要學習的是
entries
方法。entries
方法返回包含鍵值對的@@iterator
,下面是使用這個方法的代碼示例:let aEntries = numbers.entries; // 得到鍵值對的迭代器 console.log(aEntries.next.value); // [0, 1] - 位置0的值為1 console.log(aEntries.next.value); // [1, 2] - 位置1的值為2 console.log(aEntries.next.value); // [2, 3] - 位置2的值為3
numbers
數組中都是數字,key
是數組中的位置,value
是保存在數組索引的值。使用集合、字典、散列表等數據結構時,能夠取出鍵值對是很有用的。這個功能會在本書後面的章節中大顯身手。
keys
方法返回包含數組索引的@@iterator
,下面是使用這個方法的代碼示例:let aKeys = numbers.keys; // 得到數組索引的迭代器 console.log(aKeys.next); // {value: 0, done: false } console.log(aKeys.next); // {value: 1, done: false } console.log(aKeys.next); // {value: 2, done: false }
keys
方法會返回numbers
數組的索引。一旦沒有可迭代的值,aKeys.next
就會返回一個value
屬性為undefined
,done
屬性為true
的對象。如果done
屬性的值為false
,就意味著還有可迭代的值。values
方法返回的@@iterator
則包含數組的值。使用這個方法的代碼示例如下:let aValues = numbers.values; console.log(aValues.next); // {value: 1, done: false } console.log(aValues.next); // {value: 2, done: false } console.log(aValues.next); // {value: 3, done: false }
記住,當前的瀏覽器還沒有完全支持ES6所有的新功能,因此,測試這些代碼最好的辦法是使用Babel。訪問https://goo.gl/eojEGk查看和運行示例。
使用
from
方法Array.from
方法根據已有的數組創建一個新數組。比如,要複製numbers
數組,可以這樣做:et numbers2 = Array.from(numbers);
還可以傳入一個用來過濾值的函數,例子如下:
let evens = Array.from(numbers, x => (x % 2 == 0));
上面的代碼會創建一個
evens
數組,其中只包含numbers
數組中的偶數。訪問https://goo.gl/n4rOY4查看和運行示例。
使用
Array.of
方法Array.of
方法根據傳入的參數創建一個新數組。以下面的代碼為例:let numbers3 = Array.of(1); let numbers4 = Array.of(1, 2, 3, 4, 5, 6);
它和下面這段代碼的效果一樣:
let numbers3 = [1]; let numbers4 = [1, 2, 3, 4, 5, 6];
我們也可以用這個方法複製已有的數組,比如:
let numbersCopy = Array.of(...numbers4);
上面的代碼和
Array.from(numbers4)
的效果是一樣的,區別只是用到了第1章講過的展開操作符。展開操作符(...
)會把numbers4
數組裡的值都展開成參數。訪問https://goo.gl/FoJYNf查看和運行示例。
使用
fill
方法fill
方法用靜態值填充數組。以下面的代碼為例:let numbersCopy = Array.of(1, 2, 3, 4, 5, 6);
numbersCopy
數組的length
是6
,也就是有6個位置。再看下面的代碼:numbersCopy.fill(0);
numbersCopy
數組所有位置的值都會變成0
([0, 0, 0, 0, 0, 0]
)。我們還可以指定開始填充的索引,如下:
numbersCopy.fill(2, 1);
上面的例子裡,數組中從1開始的所有位置,值都是
2
([0, 2, 2, 2, 2, 2]
)。同樣,也可以指定結束填充的索引:
numbersCopy.fill(1, 3, 5);
上面的例子裡,我們會把
1
填充到數組索引3
到5
的位置(不包括5
),得到的數組為[0, 2, 2, 1, 1, 2]
。創建數組並初始化值的時候,
fill
方法非常好用,就像下面這樣:let ones = Array(6).fill(1);
上面的代碼創建了一個長度為
6
,所有的值都是1
的數組([1, 1, 1, 1, 1, 1]
)。訪問https://goo.gl/sqiHSK查看和運行示例。
使用
copyWithin
方法copyWithin
方法複製數組中的一系列元素到同一數組指定的起始位置。看看下面這個例子:let copyArray = [1, 2, 3, 4, 5, 6];
假如我們想把
4
、5
、6
三個值複製到數組前三個位置,得到[4, 5, 6, 4, 5, 6]
這個數組。可以用下面的代碼達到目的:copyArray.copyWithin(0, 3);
假如我們想把
4
、5
兩個值(位置3和4)複製到位置1和2,可以這樣做:copyArray = [1, 2, 3, 4, 5, 6]; copyArray.copyWithin(1, 3, 5);
這種情況下,會把從位置3開始到位置5結束(不包括5)的元素複製到位置1,結果是得到數組
[1, 4, 5, 4, 5, 6]
。訪問https://goo.gl/hZhBE1查看和運行示例。
2.7.4 排序元素
通過本書,我們能學到如何編寫最常用的搜索和排序算法。其實,JavaScript裡也提供了一個排序方法和一組搜索方法。讓我們來看看。
首先,我們想反序輸出數組numbers
(它本來的排序是1, 2, 3, 4,…15)。要實現這樣的功能,可以用reverse
方法,然後數組內元素就會反序。
numbers.reverse;
現在,輸出numbers
的話就會看到[15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
。然後,我們用sort
方法:
numbers.sort;
然而,如果輸出數組,結果會是[1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5, 6, 7, 8, 9]
。看起來不大對,是吧?這是因為sort
方法在對數組做排序時,把元素默認成字符串進行相互比較。
我們可以傳入自己寫的比較函數。因為數組裡都是數字,所以可以這樣寫:
numbers.sort(function(a, b){
return a-b;
});
這段代碼,在b
大於a
時,會返回負數,反之則返回正數。如果相等的話,就會返回0。也就是說返回的是負數,就說明a
比b
小,這樣sort
就根據返回值的情況給數組做排序。
之前的代碼也可以被表示成這樣,會更清晰一些:
function compare(a, b) {
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
// a必須等於b
return 0;
}
numbers.sort(compare);
這是因為JavaScript的sort
方法接受compareFunction
作為參數,然後sort
會用它排序數組。在例子裡,我們聲明了一個用來比較數組元素的函數,使數組按升序排序。
自定義排序
我們可以對任何對像類型的數組排序,也可以創建
compareFunction
來比較元素。例如,對像Person
有名字和年齡屬性,我們希望根據年齡排序,就可以這麼寫:var friends = [ {name: 'John', age: 30}, {name: 'Ana', age: 20}, {name: 'Chris', age: 25} ]; function comparePerson(a, b){ if (a.age < b.age){ return -1 } if (a.age > b.age){ return 1 } return 0; } console.log(friends.sort(comparePerson));
在這個例子裡,最後會輸出
Ana(20), Chris(25), John(30)
。字符串排序
假如有這樣一個數組:
var names =['Ana', 'ana', 'john', 'John']; console.log(names.sort);
你猜會輸出什麼?答案是這樣的:
["Ana", "John", "ana", "john"]
既然a在字母表裡排第一位,為何
ana
卻排在了John
之後呢?這是因為JavaScript在做字符比較的時候,是根據字符對應的ASCII值來比較的。例如,A、J、a、j對應的ASCII值分別是65、75、97、106。雖然在字母表裡a是最靠前的,但J的ASCII值比a的小,所以排在a前面。
想瞭解更多關於ASCII表的信息,請訪問http://www.asciitable.com/。
現在,如果給
sort
傳入一個忽略大小寫的比較函數,將會輸出["Ana", "ana", "John", "john"]
:names.sort(function(a, b){ if (a.toLowerCase < b.toLowerCase){ return -1 } if (a.toLowerCase > b.toLowerCase){ return 1 } return 0; });
假如對帶有重音符號的字符做排序的話,我們可以用
localCompare
來實現:var names2 = ['Maeve', 'Maeve']; console.log(names2.sort(function(a, b){ return a.localCompare(b); }));
最後輸出的結果將是
["Maeve", "Maeve"]
。
2.7.5 搜索
搜索有兩個方法:indexOf
方法返回與參數匹配的第一個元素的索引,lastIndexOf
返回與參數匹配的最後一個元素的索引。我們來看看之前用過的numbers
數組:
console.log(numbers.indexOf(10));
console.log(numbers.indexOf(100));
在這個示例中,第一行的輸出是9
,第二行的輸出是-1
(因為100不在數組裡)。
下面的代碼會返回同樣的結果:
numbers.push(10);
console.log(numbers.lastIndexOf(10));
console.log(numbers.lastIndexOf(100));
我們往數組裡加入了一個新的元素10
,因此第二行會輸出15
(數組中的元素是1到15,還有10),第三行會輸出-1
(因為100不在數組裡)。
ECMAScript 6——
find
和findIndex
方法看看下面這個例子:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; function multipleOf13(element, index, array) { return (element % 13 == 0) ? true : false; } console.log(numbers.find(multipleOf13)); console.log(numbers.findIndex(multipleOf13));
find
和findIndex
方法接收一個回調函數,搜索一個滿足回調函數條件的值。上面的例子裡,我們要從數組裡找一個13的倍數。find
和findIndex
的不同之處在於,find
方法返回第一個滿足條件的值,findIndex
方法則返回這個值在數組裡的索引。如果沒有滿足條件的值,find
會返回undefined
,而findIndex
返回-1
。訪問https://goo.gl/2vAaCh查看和運行示例。
ECMAScript 7——使用
includes
方法如果數組裡存在某個元素,
includes
方法會返回true
,否則返回false
。使用includes
方法的例子如下:console.log(numbers.includes(15)); console.log(numbers.includes(20));
例子裡的
includes(15)
返回true
,includes(20)
返回false
,因為numbers
數組裡沒有20
。如果給
includes
方法傳入一個起始索引,搜索會從索引指定的位置開始:let numbers2 = [7, 6, 5, 4, 3, 2, 1]; console.log(numbers2.includes(4, 5));
上面的例子輸出為
false
,因為數組索引5
之後的元素不包含4
。訪問https://goo.gl/tTY9bc查看和運行示例。
2.7.6 輸出數組為字符串
現在,我們學習最後兩個方法:toString
和join
。
如果想把數組裡所有元素輸出為一個字符串,可以用toString
方法:
console.log(numbers.toString);
1
、2
、3
、4
、5
、6
、7
、8
、9
、10
、11
、12
、13
、14
、15
和10
這些值都會在控制台中輸出。
如果想用一個不同的分隔符(比如-
)把元素隔開,可以用join
方法:
var numbersString = numbers.join('-');
console.log(numbersString);
這將輸出:
1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-10
如果要把數組內容發送到服務器,或進行編碼(知道了分隔符,解碼也很容易),這會很有用。
有一些很棒的資源可以幫助你更深入地瞭解數組及其方法。
第一個是w3schools的數組頁面:http://www.w3schools.com/js/js_arrays.asp。
第二個是w3schools的數組方法頁面:http://www.w3schools.com/js/js_array_methods.asp。
Mozilla的數組及其方法的頁面也非常棒,還有不錯的例子:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array(http://goo.gl/vu1diT)。
在JavaScript項目中使用數組時,也有一些很棒的類庫。
Underscore:http://underscorejs.org/
Lo-Dash:http://lodash.com/-