Learning Objectives
- 建立陣列,用索引與 length 存取元素
- 用 push、pop、shift、unshift、splice 改變陣列,用 slice、join、indexOf、includes 取得新的值
- 用 forEach、map、filter、reduce、find 處理每一個元素
- 用比較函式正確排序數字與物件
- 說明陣列是參照,並用展開語法或 slice 複製陣列
為什麼需要陣列
要記錄三位同學的數學成績,可以宣告三個變數:
let aMath = 100;
let bMath = 90;
let cMath = 60;
但如果有 50 位同學,就要 50 個變數,要加總、找最高分都得一個一個寫。陣列(array)把一群值放在同一個名稱底下, 用索引(index)分辨:
const scores = [100, 90, 60];
scores[0]; // 100:索引從 0 開始
scores.length; // 3:元素的個數
- 陣列是有順序的:索引 0 是第一個,
length - 1是最後一個 - 元素可以是任何型別,同一個陣列裡也可以混合(但實際使用時,通常一個陣列放同一種資料)
- 搭配迴圈,不管有幾個元素,程式都一樣長
建立陣列一般用 [ ];new Array('Red', 'Green') 也可以,但 new Array(3) 會建立「3 個空位」而不是 [3],容易搞混,所以很少用。
索引、length 與搜尋
注意第 7 行指定 colors[3] 之後陣列變長;第 9 行把 length 改成 2,後面兩個元素就不見了。indexOf 找不到時回傳 -1。
加入、取出與 splice
| 方法 | 作用 | 回傳 |
|---|---|---|
push(x, …) |
加到最後面(可以一次加好幾個) | 新的長度 |
pop() |
取出最後一個 | 被取出的元素 |
unshift(x, …) |
加到最前面 | 新的長度 |
shift() |
取出第一個 | 被取出的元素 |
splice(start, 刪幾個, 要放入的…) |
從 start 刪掉幾個元素,再放入新的元素 |
被刪掉的元素組成的陣列 |
這五個方法都會改變原本的陣列。
splice 一個方法就能刪除、插入或取代:
a.splice(5, 1); // 刪掉索引 5 的一個元素
a.splice(2, 0, 99); // 刪 0 個:在索引 2 插入 99
a.splice(5, 3, 20, 30); // 刪掉 3 個,換成 20、30
舊教材會提到 delete a[1]:它不會把元素移除,只會在那個位置留下空位,length 不變,所以不要用它刪陣列元素。
push、pop、shift、unshift
每一步看右邊 fruits 的變化。push 回傳的是新的長度(3),pop 和 shift 回傳被取出的元素。
splice 改變原陣列,slice 不會
對照第 3 行的 splice 和第 9 行的 slice:splice 執行後右邊的 a 改變了;slice 產生新的陣列 part,a 不變。
想一想:陣列的方法
下面的程式會印出什麼?
const stack = [1, 2, 3];
const x = stack.pop();
stack.push(9);
console.log(x, stack);
下面的程式會印出什麼?
const letters = ['a', 'b', 'c', 'd', 'e'];
console.log(letters.slice(1, 3));
下面的程式會印出什麼?
const a = [10, 20, 30, 40];
const r = a.splice(1, 2);
console.log(a.length, r.length);
把函式交給陣列:forEach、map、filter、reduce、find
這些方法都收一個函式當引數,陣列會對每個元素呼叫一次這個函式(這種被別人呼叫的函式叫 callback)。
callback 依序收到 (元素, 索引, 整個陣列),用不到的參數可以不寫。
| 方法 | callback 回傳什麼 | 方法本身回傳 | 原陣列 |
|---|---|---|---|
forEach(fn) |
不需要 | undefined |
不變 |
map(fn) |
新的值 | 每個元素換成回傳值的新陣列(長度相同) | 不變 |
filter(fn) |
true/false | 回傳 true 的元素組成的新陣列 | 不變 |
find(fn) |
true/false | 第一個回傳 true 的元素;沒有就是 undefined |
不變 |
findIndex(fn) |
true/false | 第一個符合的索引;沒有就是 -1 | 不變 |
reduce(fn, 初始值) |
累計到目前的結果 | 最後的累計結果 | 不變 |
reduce 的 callback 收到 (累計值, 元素):第一次的累計值是你給的初始值,之後每一次是上一次的回傳值。
[1, 2, 3].reduce((sum, n) => sum + n, 0);
// 第 1 次:sum = 0, n = 1 → 1
// 第 2 次:sum = 1, n = 2 → 3
// 第 3 次:sum = 3, n = 3 → 6
上面的 s => s >= 60 是箭頭函式,第 11 課〈函式〉會詳細介紹;現在先把它讀成「收到 s,回傳 s >= 60」。
filter、map、reduce、find 與 forEach
每呼叫一次 callback,右邊就會多出一個「(箭頭函式)」的框架,裡面的 s 是這一次拿到的元素,框架下方會顯示這一次的回傳值。注意 find 找到 85 之後就停了,不會再看 99。
sort:排數字一定要給比較函式
sort() 不給引數時,會把每個元素轉成字串再依字元順序排序。所以 [10, 1, 5, 100].sort() 會得到 [1, 10, 100, 5]:
字串 '100' 的第一個字元 '1' 比 '5' 小。
排數字要傳入比較函式 (a, b) => …,sort 會拿兩個元素問它:
| 比較函式回傳 | 意思 |
|---|---|
| 負數 | a 排在 b 前面 |
| 正數 | b 排在 a 前面 |
| 0 | 兩個順序不變 |
所以 (a, b) => a - b 是由小到大,(a, b) => b - a 是由大到小。排物件時比較它們的某個屬性,例如 (p, q) => p.age - q.age。
sort 和 reverse 都會改變原陣列。不想改到原陣列時,先複製再排序([...nums].sort(…));
較新的瀏覽器(2023 年起各主要瀏覽器都已支援)也可以用 nums.toSorted(…),它回傳排好的新陣列,原陣列不變。
數字排序與物件排序
第 5 行的 sort() 沒有比較函式,排出來的順序很奇怪;第 8 行加上 (a, b) => a - b 之後,每一次比較都會出現一個框架,你可以看到 a、b 是哪兩個元素,以及回傳值的正負。
陣列也是參照
陣列是物件的一種,所以和上一課的物件一樣:const arr2 = arr1 只是讓兩個變數指向同一個陣列,透過哪一個變數修改,兩邊都看得到。
要複製一份新的陣列,常用:
const copy1 = [...arr1]; // 展開語法
const copy2 = arr1.slice(); // slice 不給引數:從頭複製到尾
const copy3 = Array.from(arr1);
這些都是淺複製:如果元素本身是物件,新陣列裡放的仍是同一個物件的參照(第 10 課會說明深複製)。
走訪陣列用一般的 for(需要索引時)或 for…of(只需要值時),或是 forEach。不要用 for…in:它拿到的是索引字串,
還可能走訪到陣列上自訂的其他屬性。
指定與複製
看右邊的箭頭:arr1、arr2 指向同一個陣列;arr3、arr4 各自是新的陣列。
想一想:callback 與排序
下面的程式會印出什麼?
console.log([10, 1, 5, 100].sort());
下面的程式會印出什麼?
const nums = [1, 2, 3, 4];
const result = nums.filter(n => n % 2 === 0).map(n => n * 10);
console.log(result, nums.length);
下面的程式會印出什麼?
const prices = [30, 50, 20];
const total = prices.reduce((sum, p) => sum + p, 100);
console.log(total);
實作:找出及格的同學
下面是十位同學的分數。請完成三件事:用 filter 找出超過 60 分的分數、印出共有幾位、用 reduce 算出全班平均(保留一位小數,用 toFixed(1))。
重點精華回顧
- 陣列是有順序的一組值,用
[ ]建立;索引從 0 開始,length是元素個數,最後一個是arr[arr.length - 1]。 push/pop處理最後面,unshift/shift處理最前面,splice可以刪除、插入、取代;這些方法都會改變原陣列。slice取出一段成為新陣列、join接成字串、indexOf找索引(找不到是 -1)、includes回答有沒有。forEach逐一處理、map轉換、filter篩選、find找第一個、reduce累計;它們都不改變原陣列。sort()預設依字串排序,排數字要寫sort((a, b) => a - b);sort會改變原陣列。- 陣列是參照;要複製用
[...arr]或arr.slice()。走訪陣列用for、for…of或forEach,不要用for…in。