JavaScript 陣列方法深度解析:forEach、map 與 reduce

作者: Calpa Liu
字數:1589
出版:April 12, 2025

JavaScript 作為一門功能強大的程式語言,提供了多種操作陣列的高階函數方法,其中 forEach、map 和 reduce 是最常用且最具表達力的三種方法。這些方法為開發者提供了簡潔、可讀性高的寫法,能夠以宣告式而非命令式的方式處理陣列資料。本文將深入探討這三種方法的原理、使用方式、差異及適用場景,幫助開發者在實際專案中更有效地選擇和使用它們。

基本概念與原理

這三種陣列方法都是迭代陣列元素並執行指定函數的工具,但它們各自有不同的用途和行為模式。在了解它們的具體用法前,我們需要明白它們的共同點:它們都在內部使用迴圈來遍歷陣列元素,讓開發者能夠以更抽象的方式處理陣列操作。

forEach:執行副作用的迭代器

forEach 方法是最基本的陣列迭代方法,它對陣列中的每個元素執行一次提供的函數,但不返回任何值(即返回 undefined)。

array.forEach(function(currentValue, index, array) {
  // 對每個元素執行的程式碼
});

此方法的主要特點是:

  • 不返回新陣列,而是可能修改原陣列或產生副作用
  • 無法中斷迴圈(除非拋出異常)
  • 返回 undefined,因此不能鏈式調用其他陣列方法

map:轉換陣列的工具

map 方法建立一個新陣列,其中包含對原陣列中每個元素調用提供函數後的結果:

const newArray = array.map(function(currentValue, index, array) {
  // 返回轉換後的元素
  return transformedValue;
});

map 方法的關鍵特性:

  • 返回一個新陣列,長度與原陣列相同
  • 不修改原陣列(除非在回調函數中明確操作原陣列)
  • 非常適合將資料從一種形式轉換為另一種形式

reduce:聚合陣列的多功能工具

reduce 方法將陣列中的所有值聚合為單一值,它對每個元素執行一個 reducer 函數:

const result = array.reduce(function(accumulator, currentValue, index, array) {
  // 返回更新後的累加器
  return updatedAccumulator;
}, initialValue);

reduce 方法的獨特之處:

  • 需要一個累加器(accumulator)參數
  • 可以指定初始值(initialValue)
  • 最終返回一個單一值,可以是任何類型(數字、字串、物件、陣列等)
  • 極其靈活,可以用於多種複雜的資料轉換操作

深入比較:forEach vs map vs reduce

返回值的差異

這三種方法最根本的區別在於它們的返回值:

  • forEach 返回 undefined
  • map 返回新陣列
  • reduce 返回單一聚合值

這一差異直接影響了它們的可鏈式調用能力:

const myArray = [1, 2, 3, 4, 5];

// 這會拋出錯誤,因為 forEach 返回 undefined
myArray.forEach(x => x * x).reduce((total, value) => total + value);

// 這可以正常工作,因為 map 返回一個新陣列
myArray.map(x => x * x).reduce((total, value) => total + value); // 返回 55

此例清楚地展示了為何在需要進一步處理結果時,map 比 forEach 更適合。

可變性與不可變性

從函數式程式設計的角度看,map 和 reduce 通常與不可變性(immutability)相關聯,而 forEach 則傾向於可變操作:

  • forEach 經常用於修改外部狀態或執行副作用
  • map 通常用於產生新的資料結構,不修改原始資料
  • reduce 則用於轉換/聚合資料為新的形式
const items = [{ id: 1 }, { id: 2 }];

// 使用 forEach 修改原物件(可變操作)
items.forEach(item => {
  item.userId = '#abc';
});

// 使用 map 創建新物件(不可變操作)
const newItems = items.map(item => {
  return { ...item, userId: '#abc' };
});

尤雨溪的形象比喻

Vue.js 的創建者尤雨溪提供了一個非常生動的比喻來解釋這些方法:

想像你面前站了一排人(陣列中的元素):

  • forEach 就像你依次對每個人做些什麼,可能是任何操作
  • map 就像你拿著一個盒子,讓每個人將錢包扔進去,最終得到一個裝滿錢包的新盒子
  • reduce 則像是你計算所有人錢包中的錢,最終得到一個總金額

實際使用案例與範例

forEach 適用場景

forEach 最適合當你需要執行副作用而不關心返回值的情況:

// 向後端 API 發送數據
users.forEach(user => {
  sendDataToAPI(user.id);
});

// 更新 DOM 元素
elements.forEach(element => {
  element.classList.add('active');
});

map 適用場景

map 非常適合數據轉換場景,特別是在需要保留原始數據的情況下:

// 轉換用戶資料格式
const userNames = users.map(user => {
  return {
    fullName: `${user.firstName} ${user.lastName}`,
    age: user.age
  };
});

// 在 React 中渲染列表
const listItems = products.map(product => (
  {product.name} - ${product.price}
));

通常在 React 中,我們會使用 map 來生成列表,因為它會返回一個新的陣列,這有助於保持資料的不可變性。

const listItems = products.map(product => (
  <li key={product.id}>{product.name} - ${product.price}</li>
));

reduce 適用場景

reduce 在需要將陣列聚合為單一值時非常有用:

// 計算總和
const sum = numbers.reduce((total, num) => total + num, 0);

// 構建複雜對象
const groupedByCategory = products.reduce((groups, product) => {
  const category = product.category;
  if (!groups[category]) {
    groups[category] = [];
  }
  groups[category].push(product);
  return groups;
}, {});

以上範例展示了如何使用 forEach、map 和 reduce 來處理不同的資料轉換場景。

最佳實踐與選擇指南

如何在這三種方法中做出選擇?以下是一些指導原則:

  1. 使用 forEach 當

    • 你只需要迭代執行操作,不需要返回值
    • 你需要執行副作用(如 DOM 操作、發送請求)
    • 你不需要中斷迭代過程
  2. 使用 map 當

    • 你需要從現有陣列創建一個新陣列
    • 你需要轉換每個元素
    • 你希望保持資料的不可變性
  3. 使用 reduce 當

    • 你需要將陣列聚合為單一值(如總和、平均值)
    • 你需要構建複雜的資料結構
    • 其他陣列方法組合起來會顯得過於複雜

結論

forEach、map 和 reduce 是 JavaScript 中處理陣列的強大工具,它們各自有其獨特的用途和優勢。了解它們之間的差異和適用場景,可以讓你的程式碼更加簡潔、可讀和高效。

雖然 forEach 側重於產生副作用,map 專注於轉換資料,reduce 善於聚合值,但在實際開發中,它們常常協同工作,共同解決複雜的資料處理問題。選擇正確的工具,不僅可以使程式碼更加清晰,還能更好地表達你的程式設計意圖。

在當代 JavaScript 程式設計中,熟練掌握這些高階函數是成為優秀開發者的基本要求。透過恰當地使用它們,你可以寫出更具表達力、更易維護的程式碼,同時充分發揮 JavaScript 的函數式程式設計特性。

感謝您閱讀我的文章。歡迎隨時分享你的想法。
關於 Calpa

Calpa 擅長使用 TypeScriptReact.jsVue.js 建立 Responsive Website。

他積極參與開源社區,曾在 2019 年的香港開源大會上擔任講者,提供工作經驗和見解。此外,他也在 GitHub 上公開分享個人博客程式碼,已獲得超過 300 顆星星和 60 個分支的支持。

他熱愛學習新技術,並樂意分享經驗。他相信,唯有不斷學習才能跟上快速演變的技術環境。