初學者指南:深入了解 JavaScript reduce() 方法
更新日期: 2025 年 1 月 23 日
本文為 JavaScript 高階函數系列文,第 4 篇
在 JavaScript 中,陣列方法提供了許多強大的工具來處理資料,reduce()
是其中最常用也最靈活的方法之一。
reduce()
允許你將陣列中的每個元素累積到一個單一的結果中,不論這個結果是數字、字串、物件,還是其他類型的資料。
這篇文章將為新手介紹 reduce()
方法,並通過簡單範例解釋它的工作原理和應用場景。
什麼是 reduce()
?
reduce()
方法會對陣列中的每個元素執行回調函數,最終將結果累積成單一的值並返回。
這個方法常用於將陣列壓縮成一個值,例如計算陣列元素的總和、乘積,甚至可以用來處理物件。
語法
array.reduce(function(accumulator, currentValue, currentIndex, array) {
// 回調函數邏輯
}, initialValue)
accumulator
:累積器,保存每次回調函數執行後的累積結果。currentValue
:當前正在處理的陣列元素。currentIndex
(可選):當前元素的索引。array
(可選):被操作的陣列。initialValue
(可選):累積器的初始值。如果不提供,默認使用陣列的第一個元素作為初始值,並從第二個元素開始迭代。
reduce()
的基本應用
計算陣列總和
讓我們從一個簡單的範例開始,計算一個數字陣列的總和。
範例:
let numbers = [1, 2, 3, 4];
let sum = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
console.log(sum); // 輸出:10
說明:
accumulator
開始時為初始值0
,然後依次將每個元素累加到累積器中。- 最終,
reduce()
返回所有數字的總和。
計算陣列的乘積
除了總和,你還可以使用 reduce()
來計算乘積。
範例:
let numbers = [1, 2, 3, 4];
let product = numbers.reduce(function(accumulator, currentValue) {
return accumulator * currentValue;
}, 1);
console.log(product); // 輸出:24
說明:
- 在這裡,我們使用初始值
1
,然後每次將累積器與當前值相乘,最後得到陣列中所有數字的乘積。
進階應用
陣列中找出最大值
reduce()
也可以用來查找陣列中的最大值。
範例:
let numbers = [10, 20, 30, 40, 50];
let max = numbers.reduce(function(accumulator, currentValue) {
return Math.max(accumulator, currentValue);
});
console.log(max); // 輸出:50
說明:
- 每次迭代時,
Math.max()
用來比較accumulator
和currentValue
,並返回較大的值。最終結果就是陣列中的最大值。
將陣列轉換為物件
reduce()
可以將一個陣列轉換成物件,這在處理複雜資料時非常實用。
範例:
let people = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 }
];
let peopleByName = people.reduce(function(accumulator, person) {
accumulator[person.name] = person.age;
return accumulator;
}, {});
console.log(peopleByName);
// 輸出:{ Alice: 25, Bob: 30, Charlie: 35 }
逐步解釋
people
陣列:首先,people
是一個陣列,其中每個元素都是一個物件,包含name
和age
的資訊。範例如下:
let people = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 35 }
];
reduce()
方法:reduce()
會遍歷整個people
陣列,並將每個元素(人員物件)依次傳入回調函數中,最終累積結果(即一個物件)會被返回。accumulator
:accumulator
是回調函數的第一個參數,它是累積結果的容器。在每次迭代中,這個累積結果會被更新。- 在這段代碼中,
accumulator
初始化為一個空物件{}
,這是在reduce()
方法的第二個參數中指定的。 accumulator
在每次迭代中會累積一個屬性,該屬性是當前遍歷的人員的name
,對應的值是該人員的age
。
person
:person
是people
陣列中的當前元素,也就是當前的每個人員物件。person
會依次從陣列中取出,並被傳入回調函數。
- 迭代過程:
- 在每次迭代中,
person
的name
屬性被用作accumulator
物件的鍵,而age
屬性則被用作該鍵對應的值。
- 在每次迭代中,
- 代碼詳解:
accumulator[person.name] = person.age;
:這行代碼動態地為accumulator
物件新增屬性,person.name
作為屬性名,person.age
作為該屬性的值。- 然後
return accumulator;
:將更新過的accumulator
返回,這樣它能夠在下一次迭代中繼續累積新的值。
- 最終結果:
- 當所有的陣列元素都被處理後,
accumulator
(即peopleByName
) 將是一個物件,其中每個人的名字是物件的屬性,對應的值是年齡。
- 當所有的陣列元素都被處理後,
那麼,這段代碼最終會生成以下物件:
let peopleByName = {
Alice: 25,
Bob: 30,
Charlie: 35
};
計算陣列中元素的出現次數
你也可以使用 reduce()
來計算陣列中每個元素出現的次數。
範例:
let fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
let fruitCount = fruits.reduce(function(accumulator, fruit) {
if (accumulator[fruit]) {
accumulator[fruit]++;
} else {
accumulator[fruit] = 1;
}
return accumulator;
}, {});
console.log(fruitCount);
// 輸出:{ apple: 3, banana: 2, orange: 1 }
說明:
這段代碼的目的是計算 fruits
陣列中每種水果出現的次數,並將結果存放在一個物件中,最終輸出如下:
{ apple: 3, banana: 2, orange: 1 }
代碼逐步解釋
fruits
陣列:這是一個包含多個水果名稱的陣列,其中一些水果是重複的。
我們的目標是計算每種水果在陣列中出現的次數。
let fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
reduce()
方法:reduce()
是一個強大的陣列方法,它用於累積陣列中的值,並生成一個最終結果。
這裡,我們將使用reduce()
將水果出現的次數累積到一個物件中。 語法如下:
array.reduce(function(accumulator, currentValue) {
// 操作邏輯
}, initialValue);
accumulator
:累積器,用來保存計數結果,初始值是{}
(一個空物件)。fruit
:當前正在遍歷的水果元素,即陣列中的每一個水果。initialValue
:在這裡我們指定為一個空物件{}
,它用來存放每種水果的出現次數。
- 累積器的作用:
accumulator
是一個物件,用來保存每種水果的名稱作為鍵,並將該水果出現的次數存儲為鍵值對的值。
- 回調函數邏輯:每次迭代時,我們會檢查當前水果(
fruit
)是否已經存在於accumulator
物件中。
如果存在,則將其對應的計數值加 1。如果不存在,則將該水果新增到accumulator
中,並將其計數設為 1。
詳解:if (accumulator[fruit])
:
檢查accumulator
中是否已經有當前的fruit
。如果有(即鍵已經存在),那麼就進行加 1 操作,表示該水果已經出現過。accumulator[fruit]++
:
如果該水果已經在accumulator
中,則將其對應的計數值加 1。else
塊:
如果該水果尚未出現過,則進入else
,在accumulator
中新增該水果作為鍵,並將其值設為 1,表示該水果第一次出現。
- 返回累積器:每次回調函數執行完畢時,
reduce()
會返回更新過的accumulator
,並將其傳遞給下一次迭代。 - 最終結果:當所有的水果都被遍歷完後,
reduce()
的最終結果會是一個物件,這個物件包含了每種水果的出現次數。
範例執行流程:
以 fruits
陣列為例,我們逐步跟蹤 reduce()
如何處理這個陣列。
第一次迭代:
fruit = 'apple'
accumulator = {}
(初始化為空物件)'apple'
不在accumulator
中,於是accumulator['apple'] = 1
。
此時 accumulator
變成:
{ apple: 1 }
第二次迭代:
fruit = 'banana'
'banana'
不在accumulator
中,於是accumulator['banana'] = 1
。
此時 accumulator
變成:
{ apple: 1, banana: 1 }
第三次迭代:
fruit = 'apple'
'apple'
已經存在於accumulator
中,於是accumulator['apple']++
,即計數加 1。
此時 accumulator
變成:
{ apple: 2, banana: 1 }
第四次迭代:
fruit = 'orange'
'orange'
不在accumulator
中,於是accumulator['orange'] = 1
。
此時 accumulator
變成:
{ apple: 2, banana: 1, orange: 1 }
第五次迭代:
fruit = 'banana'
'banana'
已經存在於accumulator
中,於是accumulator['banana']++
,即計數加 1。
此時 accumulator
變成:
{ apple: 2, banana: 2, orange: 1 }
第六次迭代:
fruit = 'apple'
'apple'
已經存在於accumulator
中,於是accumulator['apple']++
,即計數加 1。
此時最終的 accumulator
變成:
{ apple: 3, banana: 2, orange: 1 }
最終結果
當所有元素都被處理完後,fruitCount
會得到以下結果:
{ apple: 3, banana: 2, orange: 1 }
這個物件表明 apple
出現了 3 次,banana
出現了 2 次,而 orange
出現了 1 次。
在進階應用部分,可以新增以下區塊來解釋該代碼的應用原理:
reduce()
代碼輸出特定結果
這段代碼用 reduce()
遍歷數組,但每次迭代的回調函數都返回 1
,而不是使用累積結果,因此輸出的是單一的值 1
,而非基於累積計算的結果。
const number = [1, 3, 4, 7, 8, 9];
number.reduce((acc, cv) => {
return 1;
}, 0);
每次迭代回傳 1
,累積值被忽略,最終結果為 1
。
在這段 reduce()
代碼中,累積器 acc
的值會隨著每次迭代的返回值進行更新,因此理解每次迭代的過程對理解 reduce()
很重要。
第一輪迭代:
- 初始值:
acc
的初始值是0
,這是通過reduce
的第二個參數設置的。 cv
(當前值):cv
是數組的第一個元素1
。- 返回值:這一輪的回調函數僅返回固定的
1
,因此下一輪的acc
將是1
。
第二輪迭代:
acc
:這一輪的acc
是上一輪返回的值,即1
。cv
(當前值):這次的cv
是數組的第二個元素3
。- 返回值:再次,回調函數返回
1
,這使得acc
仍然保持為1
,而不受cv
值的影響。
第三輪迭代:
acc
:此時的acc
繼續保持1
。cv
(當前值):現在的cv
是數組的第三個元素4
。- 返回值:回調函數依然返回固定的
1
,因此acc
依舊維持不變。
第四輪迭代:
acc
:此輪的acc
還是1
,因為每一輪的返回值都固定為1
。cv
(當前值):這次的cv
是數組的第四個元素7
。- 返回值:回調函數繼續返回
1
,導致acc
保持為1
。
第五輪迭代:
acc
:acc
再次保持為1
。cv
(當前值):這次的cv
是數組的第五個元素8
。- 返回值:回調函數返回
1
,使acc
繼續保持1
。
第六輪迭代:
acc
:累積器acc
仍然是1
。cv
(當前值):最後一個cv
是數組的第六個元素9
。- 返回值:回調函數再次返回
1
,結束所有迭代。
整個過程中,無論 cv
的值如何變化,回調函數總是返回固定的 1
,所以每一輪的 acc
都被更新為 1
,並且保持不變。
最終,reduce()
的結果也就是 1
,而不是基於數組元素的任何計算結果。
提供 initialValue
的重要性
在使用 reduce()
時,提供初始值 initialValue
通常是很重要的。
這可以確保累積器有一個確定的初始狀態,並且能夠在處理空陣列,或非數值類型的資料時避免錯誤。
初始值對空陣列的影響
範例:
let numbers = [];
let sum = numbers.reduce(function(accumulator, currentValue) {
return accumulator + currentValue;
}, 0);
console.log(sum); // 輸出:0
如果我們沒有提供初始值,reduce()
會嘗試將陣列中的第一個元素作為初始值,這在處理空陣列時會導致錯誤。
提供 initialValue
能夠確保即使陣列為空,累積器仍然能夠正常工作。
reduce()
vs 其他陣列方法
你可能會發現,reduce()
可以用來實現許多其他陣列方法的功能,例如 map()
、filter()
或 find()
。
事實上,reduce()
是一個非常靈活和強大的工具,它可以根據需要進行高度自定義。
例如,用 reduce()
來實現一個簡單的 map()
功能:
let numbers = [1, 2, 3, 4];
let squared = numbers.reduce(function(accumulator, currentValue) {
accumulator.push(currentValue * currentValue);
return accumulator;
}, []);
console.log(squared); // 輸出:[1, 4, 9, 16]
這個範例顯示了如何使用 reduce()
將每個數字平方並存儲在一個新的陣列中。
雖然 map()
專門用於這種操作,但 reduce()
也能夠達到同樣的效果,並且提供了更大的靈活性。
結語
JavaScript 的 reduce()
方法是一個強大且靈活的工具,能夠對陣列進行各種操作,從簡單的總和計算到更複雜的資料結構轉換。
理解 reduce()
的工作原理,將有助於你在開發中有效處理陣列資料,並且能夠將多步操作濃縮到一個清晰的流程中。
隨著你對 reduce()
的理解加深,你會發現它是 JavaScript 中不可或缺的一部分,無論是處理數值運算還是進行更複雜的資料轉換,reduce()
都能大大簡化你的程式碼。