陣列是我們在寫程式時常用的資料類型之一,常用它來處理一組資料項目的集合。JavaScript 的陣列也是物件,擁有自己的方法。我們來看一下關於陣列的一些特性。熟練這些方法,你就不必依賴撰寫迴圈來處理陣列資料,工作也會比較簡單。
我們有二種方式可以建立陣列。
一個是使用 Array
建構器函式
var pokemons = new Array("Bulbasaur", "Charmander", "Squirtle");
另一種是使用 array literal。
var pokemons = ["Bulbasaur", "Charmander", "Squirtle"];
相信經驗老道的你一定都是用第二種吧?Array literal 不僅簡單易懂,輸入的字元比較少,而且 JavaScript constructor function 會有被覆寫的可能,這表示呼叫 new Array
不一定會建立出陣列。
陣列一定會有一個length
屬性,用來表示陣列的大小。要取得陣列裡的項目,使用 index number,陣列的 index number 從 0 開始計算,最後一筆資料是第length - 1
個項目。要存取超過length
的項目,會得到undefined
,因為項目並不存在,如同存取不存在的物件一樣。以上這些你應該都知道。
pokemons[0] // Bulbasaur
pokemons[2] // Squirtle
pokemons[4] // undefined
我們可以更換陣列項目的值,只要指定 index number。
console.log(pokemons);
// ["Bulbasaur", "Charmander", "Squirtle"]
pokemons[1] = "Charizard";
console.log(pokemons);
// ["Bulbasaur", "Charizard", "Squirtle"]
如果指定的 index number 超過 length 範圍,陣列會被延展,中間沒有給值的項目如同宣告了卻不給值的變數,會是undefined
。
pokemons[5] = "Vulpix";
console.log(pokemons);
// ["Bulbasaur", "Charmander", "Squirtle", undefined, undefined, "Vulpix"]
因為length
是一個屬性,我們也可以手動更改。把length
增加的話,會新增undefined
項目,減少的話,會從陣列後面刪除多餘的項目。
console.log(pokemons.length); // 6
pokemons.length = 8;
console.log(pokemons);
// ["Bulbasaur", "Charmander", "Squirtle", undefined, undefined, "Vulpix", undefined, undefined]
pokemons.length = 3;
console.log(pokemons);
// ["Bulbasaur", "Charmander", "Squirtle"]
接著我們快速的看過內建的陣列方法。
push
: 在陣列的最尾端增加一筆資料項目unshift
:在陣列的最前端增加一筆資料項目pop
:在陣列的最尾端移除一筆資料項目shift
:在陣列的最前端移除一筆資料項目var pokemons = ["Bulbasaur", "Charmander", "Squirtle"];
pokemons.push("Pikachu");
// ["Bulbasaur", "Charmander", "Squirtle", "Pikachu"]
pokemons.unshift("Ditto");
// ["Ditto", "Bulbasaur", "Charmander", "Squirtle", "Pikachu"]
pokemons.pop();
// ["Ditto", "Bulbasaur", "Charmander", "Squirtle"]
pokemons.shift();
// ["Bulbasaur", "Charmander", "Squirtle"]
因為push
和pop
是動到尾端的項目,不會讓陣列重新調整,在處理大量資料時,會比shift
和unshift
來得快。
如果想要變更的項目不在陣列的頭尾,可以用splice
。
var pokemons = ["Bulbasaur", "Charmander", "Squirtle"];
var removed = pokemons.splice(1,1);
// pokemons: ["Bulbasaur", "Squirtle"]
// removed: ["Charmander"]
splice
吃二個參數,第一個參數是刪除起始點的 index number,第二個參數是有幾筆資料要刪除。在這裡我要刪除pokemons[1]
這一筆,所以用splice(1,1)
,用splice(1,2)
的話”Charmander”, “Squirrel”
二筆資料就會被刪掉。被刪除的項目陣列會成為splice
的回傳值。
splice
還支援在第三個參數開始輸入新的資料,會在指定的地方安插這些資料進入陣列。
最常見的迭代方法是用for
迴圈。
var pokemons = ["Bulbasaur", "Charmander", "Squirtle"];
for(var i = 0; i < pokemons.length; i++){
console.log(pokemons[i]);
}
相信你對這樣的寫法已經習以為常了。不過要輸入這麼多奇奇怪怪的符號,還蠻考驗注意力,幸好 JavaScript 提供了forEach
方法,我們寫一段 callback 函式,forEach
會將陣列中的每一個項目逐一丟到 callback 函式中操作。上面的範例改寫的話會變成下面這樣。
pokemons.forEach(function(pokemon){
// 陣列中的項目會逐一代入 pokemon 參數在此函式中執行
console.log(pokemon);
});
我們現在有一組陣列,想用這組陣列來產生另一組陣列,可以用map
方法。此方法也會將陣列中每一個項目逐一丟進 callback 函式裡,函式中的回傳值會 push 到一個新陣列,最終我們會得到一組陣列。
var pokemons = [
{name: "Bulbasaur", snack: "Razz Berry"},
{name: "Charmander", snack: "Nanab Berry"},
{name: "Squirtle", snack: "Pinap Berry"},
{name: "Pikachu", snack: "Sun Stone"}
];
// 我們的寶可夢訓練師 Ash 在超市想一次買齊所有寶可夢最愛零食,他需要一份購物清單
var snacks = pokemons.map(function(pokemon){
// 陣列中的項目會逐一代入 pokemon 參數在此函式中執行
// 回傳項目中的 snack 屬性值
return pokemon.snack;
});
// snacks: ["Razz Berry", "Nanab Berry", "Pinap Berry", "Sun Stone"];
在處理陣列時,經常會測試陣列裡的資料是否有滿足某種條件,JavaScript 提供了every
和some
二種方法。
every
用在測試所有的資料項目是否符合給定的條件,只要有一個項目不符合,其結果會是false
。
const team = [
{name: "Ash", age: 10},
{name: "Misty", age: 11},
{name: "Brock", age: 18},
{name: "Tracey", age: 15},
];
const allOldEnough = team.every(function(person){
return person.age >= 18;
});
// allOldEnough: false
Ash 的隊伍來到新的城鎮,他們打聽到市中心的酒吧可能有稀有寶可夢的情報,他們便想進去酒吧一探究竟。但是酒吧規定年滿18歲以上才能進入,門口的保鑣就用every
檢查他們每一個人,可惜 Ash 第一個就不符合了,所以隊伍也就禁止進入。
const adultPresent = team.some(function(person){
return person.age >= 18;
});
// adultPresent: true
如果酒吧的規定是只要有滿18歲的大人帶,就可以進入,這時保鑣改用some
來檢查,雖然 Ash 和 Misty 年齡還沒到,但是 Brock 已經滿18了,就得到true
的值,准許進入。
ES6 中新增了陣列find
方法,讓我們在尋找特定的陣列項目方便許多。否則以前要先建立迴圈,逐一檢查每一個項目,找到符合的項目就強迫中止迴圈,再回傳項目,實在是件麻煩的工作。
使用方法也是在find
方法的參數使用一個 callback 函式,此函式會逐一檢驗陣列項目,當遇到第一個符合條件的項目,便會將項目回傳,並終止迴圈。如果全部檢查完了卻沒有任何一個項目符合,便會回傳false
。
除此之外還有filter
的方法,用來找出陣列中所有符合條件的項目,最終集合在一個新陣列當中回傳。假如沒有任何一個項目符合篩選的條件,我們會得到一個空陣列。
const pokemons = [
{name: "Bulbasaur", type: "grass"},
{name: "Charmander", type: "fire"},
{name: "Squirtle", type: "water"},
{name: "Pikachu", type: "electric"},
{name: "Pidgey", type: "flying"},
{name: "Lapras", type: "water"},
{name: "Magikarp", type: "water"},
{name: "Vulpix", type: "fire"}
];
const firstFirePokemon = pokemons.find(function(pokemon){
return pokemon.type === "fire";
});
// {name: "Charmander", type: "fire"}
const allFirePokemons = pokemons.filter(function(pokemon){
return pokemon.type === "fire";
});
// [{name: "Charmander", type: "fire"}, {name: "Vulpix", type: "fire"}]
Ash 將要挑戰一個帶著草系寶可夢的對手,於是他用find
從自己的名單裡找出第一隻火系的寶可夢{name: "Charmander", type: "fire"}
,出來應戰。
Ash 成功的擊敗對手,但對方顯然不服氣,派出了更厲害的草系寶可夢 Venusaur!從來沒有遇過這麼強大的對手,Ash 不敢大意,於是用fiter
叫出他所有的火系寶可夢[{name: "Charmander", type: "fire"}, {name: "Vulpix", type: "fire"}]
出來,這將是場艱難的戰役。
JavaScript 陣列內建了sort
排序方法,關於它,你只要記住
Array.sort((a, b) => a - b);
就夠了。sort
方法會在背後自動幫你處理。這個 callback 函式的意思是,JavaScript 會逐一比對陣列中的任二個項目,如果函式回傳小於 0, 那麼 a
就應該在b
之前,等於 0 表示二者相等,回傳值大於 0 的話,表示a
要在b
之後。
const numberArray = [4, 2, 5, 11, 1, 3];
numberArray.sort();
// numberArray: [1, 11, 2, 3, 4, 5]
numberArray.sort((a, b) => a - b);
// numberArray: [1, 2, 3, 4, 5, 11]
const numericStringArray = ["4", "2", "5", "11", "01", "3"];
numericStringArray.sort();
// numericStringArray: ["01", "11", "2", "3", "4", "5"]
numericStringArray.sort((a, b) => a - b);
// numericStringArray: ["01", "2", "3", "4", "5", "11"]
const stringArray = ["Pikachu", "Charmander", "Squirtle", "Bulbasaur"];
stringArray.sort();
// stringArray: ["Bulbasaur", "Charmander", "Pikachu", "Squirtle"]
由上面的範例能看到,sort
方法雖然可以省略 callback 函式,在文字上面是沒問題的,會按照字母順序重排文字。由於sort
是將參數轉成字串後才進行比對,因此數字字串會以第一個字元來比對,所以說 "11"
會排在"2"
的前面,這就不會是我們要的結果。這個時候就要代入 callback 函式(a,b) => a - b
,讓我們的函式來進行比對的工作。
有時我們會需要處理一個陣列資料,最終得到一個值,例如加總陣列中所有的數字求總和。這時可以用reduce
來做,它的用途是先將陣列中第一個項目和一個初始值(accumulator
,預設是 0)丟進 callback 函式,經計算後的結果再和第二個陣列項目一起丟到函式裡計算,依此類推,最終得到一個值。
const array = [4, 2, 5, 1, 3];
const sum = array.reduce(accumulator, currentValue) => accumulator + currentValue);
// sum: 15
reduce
的用處不止這樣,它可以接受更多的參數,在許多場合會很有用,請參考MDN的文件說明。