JavaScript 陣列操作:forEach、map、filter、reduce、find、some、every
簡介
在 JavaScript 中,陣列是最常用的資料結構之一。無論是前端畫面渲染、資料處理,或是與後端 API 互動,都離不開對陣列的操作。ES5 以後,原生提供的 七大高階函式(forEach、map、filter、reduce、find、some、every)讓開發者可以用 宣告式 的方式取代傳統的 for 迴圈,寫出更簡潔、可讀性更高的程式碼。掌握這些方法不僅能提升開發效率,還能減少錯誤、提升程式的可維護性。
本篇文章將深入淺出說明每個方法的語法與使用時機,提供實務範例、常見陷阱與最佳實踐,幫助你從「會用」升級到「懂得選擇」最適合的陣列操作方式。
核心概念
1. forEach – 逐項執行副作用
forEach 會遍歷陣列的每一個元素,對每個元素呼叫一次提供的回呼函式。它不會回傳值,通常用於執行副作用(如寫入 DOM、輸出日誌等)。
const numbers = [1, 2, 3, 4, 5];
// 每個數字乘以 2 後印出
numbers.forEach((num, index) => {
console.log(`第 ${index + 1} 個元素: ${num * 2}`);
});
注意:
forEach無法中斷(不像for迴圈可以break),若需要提前結束,請改用some、every或普通for。
2. map – 產生新陣列
map 會對每個元素執行回呼函式,回傳值會組成一個全新的陣列。適合用於「資料轉換」的情境。
const users = [
{ name: 'Alice', age: 28 },
{ name: 'Bob', age: 34 },
{ name: 'Carol', age: 22 }
];
// 只取出名字
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob', 'Carol']
小技巧:若只想要簡化陣列結構,
map是首選;千萬別在map裡做副作用,會讓程式碼混亂。
3. filter – 條件篩選
filter 會依據回呼函式返回的布林值,保留符合條件的元素,形成新陣列。
const scores = [78, 92, 61, 85, 49];
// 只保留及格(>= 60)的分數
const passed = scores.filter(score => score >= 60);
console.log(passed); // [78, 92, 61, 85]
常見錯誤:回呼函式必須回傳布林值,若寫成
return score > 60 && score,會得到不預期的結果。
4. reduce – 累積運算
reduce 可把陣列「摺疊」成任意型別的單一值(數字、物件、字串…)。它接受兩個參數:累計值 (accumulator) 和當前值 (current),以及一個可選的初始值。
const expenses = [
{ category: 'food', amount: 120 },
{ category: 'transport', amount: 80 },
{ category: 'entertainment', amount: 150 }
];
// 計算總支出
const total = expenses.reduce((sum, item) => sum + item.amount, 0);
console.log(total); // 350
技巧:若需要把陣列轉成物件映射(例如分類統計),
reduce也是好幫手。
// 依類別彙總金額
const byCategory = expenses.reduce((obj, item) => {
obj[item.category] = (obj[item.category] || 0) + item.amount;
return obj;
}, {});
console.log(byCategory); // { food: 120, transport: 80, entertainment: 150 }
5. find – 找到第一個符合條件的元素
find 會回傳第一個滿足條件的元素,若找不到則回傳 undefined。
const products = [
{ id: 1, name: '筆記型電腦' },
{ id: 2, name: '智慧手機' },
{ id: 3, name: '平板電腦' }
];
const target = products.find(p => p.id === 2);
console.log(target); // { id: 2, name: '智慧手機' }
使用時機:只需要單一結果且不需要遍歷整個陣列時,
find會比filter更有效率。
6. some & every – 全部或任意符合
some:只要任一元素符合條件,就回傳true。every:全部元素都符合條件才回傳true。
const ages = [12, 25, 30, 17];
// 是否有人年滿 18 歲?
const hasAdult = ages.some(age => age >= 18);
console.log(hasAdult); // true
// 是否所有人都年滿 18 歲?
const allAdult = ages.every(age => age >= 18);
console.log(allAdult); // false
實務觀點:
some常用於權限檢查(只要有一筆符合即通過),every則適合驗證全部資料的正確性。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 忘記回傳值 | map、filter、reduce 的回呼若未 return,會得到 undefined 或錯誤的累計結果。 |
確認每個回呼都有明確的 return,或使用箭頭函式的隱式回傳(x => x * 2)。 |
| 改變原陣列 | forEach、map、filter、reduce 都不會改變原陣列,但若在回呼內直接改寫 array[i],會產生副作用。 |
保持函式純粹:不要在高階函式內直接修改來源陣列。 |
使用 map 只為副作用 |
把 map 當作 forEach 使用(只執行 console.log),會浪費記憶體產生不必要的陣列。 |
需要副作用時直接使用 forEach;需要產生新陣列時才用 map。 |
reduce 初始值遺漏 |
未提供初始值時,reduce 會把陣列第一個元素當作累計值,當陣列為空會拋錯。 |
總是提供初始值(如 0、[]、{}),確保安全性。 |
find 與 filter 混用 |
想要單一結果卻用了 filter,會得到長度為 1 的陣列,需要再取 [0]。 |
直接使用 find,語意更清晰且效能較佳。 |
some、every 內部副作用 |
在 some/every 的回呼裡改變外部變數,可能導致難以追蹤的錯誤。 |
保持回呼純粹,僅回傳布林值。 |
最佳實踐:
- 先思考意圖:是「轉換」(
map)、 「篩選」(filter)、 「彙總」(reduce)、還是「搜尋」(find)? - 保持函式純粹:避免在回呼裡改變外部狀態。
- 提供初始值:尤其使用
reduce時,永遠給一個明確的初始值。 - 利用鏈式呼叫:多個操作可以串在一起,寫出流暢的資料管線。
// 範例:先篩選、再轉換、最後統計總和
const totalPrice = products
.filter(p => p.stock > 0) // 只要有庫存
.map(p => p.price * p.quantity) // 計算每項小計
.reduce((sum, sub) => sum + sub, 0); // 加總
實際應用場景
| 場景 | 典型使用方法 | 為何適合 |
|---|---|---|
| 表單驗證 | every |
必須所有欄位都符合驗證規則才送出。 |
| 使用者權限檢查 | some |
只要使用者擁有任一必要權限即可通過。 |
| 從 API 取得大量資料,取出關鍵欄位 | map |
把原始 JSON 轉成簡化的陣列供 UI 使用。 |
| 分頁或搜尋結果的即時過濾 | filter |
依使用者輸入的關鍵字即時篩選清單。 |
| 統計圖表的資料彙整 | reduce |
把每日交易紀錄彙總成月份報表。 |
| 找出第一筆符合條件的錯誤日誌 | find |
只需要第一個符合條件的 log 來定位問題。 |
| 遍歷資料寫入資料庫 | forEach |
每筆資料執行非同步寫入,無需返回值。 |
範例: 假設你在開發一個電商後台,需要「列出本月銷售額前 5 名的商品」:
// 假設 sales 為本月每筆訂單資料
const topProducts = sales
.reduce((map, order) => {
map[order.productId] = (map[order.productId] || 0) + order.amount;
return map;
}, {}) // 依商品彙總銷售額
.Object.entries(map) // 轉成陣列 [[id, amount], ...]
.sort((a, b) => b[1] - a[1]) // 依金額降序
.slice(0, 5) // 前 5 名
.map(([id, amount]) => ({ id, amount })); // 轉回物件陣列
console.table(topProducts);
此範例結合了 reduce → Object.entries → sort → slice → map,展示了高階函式在實務中的威力。
總結
forEach用於副作用,不回傳值;map產生新陣列,適合資料轉換;filter用於條件篩選;reduce是最強大的累積/彙總工具,可產生任意型別的結果;find直接返回第一筆符合條件的元素;some與every分別檢查任一或全部條件。
掌握這七大方法後,你可以以 宣告式、可組合 的方式處理各種陣列需求,寫出更簡潔、可讀、可維護的 JavaScript 程式碼。未來在面對複雜資料流時,記得先抽象出「我要做什麼」的意圖,再選擇最貼切的陣列方法,讓程式碼自然流暢,開發效率倍增。祝你寫程式愉快! 🚀