本文為 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() 都能大大簡化你的程式碼。