JavaScript reduce 在做什麼?
陣列方法有很多種,包括 forEach
、map
、filter
等等,其中 reduce
算是比較複雜且容易讓人感到困惑的一種方法,因此這篇文章會介紹 JavaScript reduce
的功能與基本應用。
簡介
關於 reduce
MDN 的定如下:
The reduce() method executes a reducer function (that you provide) on each member of the array resulting in a single output value.
簡單來說就是 reduce
方法跟其他陣列方法(例如:map
、filter
)的差別是它會 return 一個值,而不是一個新陣列,這會連帶使 reduce
的語法結構跟邏輯與其他方法不太相同。
reduce 的基礎語法
Array.reduce(callback[accumulator, currentValue, currentIndex, array], initialValue)
看起來有點複雜,其實就是以下這五個參數:
- accumulator:經由個別
currentValue
加總的累計值 - currentValue:
Array
的個別 item - currentIndex:
Array
item 的索引 - array:呼叫該
Array method
的陣列 - initialValue:預設值,放在
function
的最後方,非必填
其中比較特別的是 accumulator
與 initialValue
,在其他的陣列方法裡也少見。用文字敘述可能會不太清楚這些參數的意思,這裡用最簡單的陣列數值加總為例,並預設兩種不同狀況:
1. 未提供 initialValue
(預設值)
const arr = [1, 2, 3, 4, 5];
const reduceArr = arr.reduce((accumulator, currentValue) => {
return accumulator + currentValue
});
console.log(reduceArr); // 15
上述範例若未提供預設值,accumulator
(累計值)就會取陣列的第一個元素也就是 1,而 currentValue
就會從陣列的第二個值開始 loop。我們可以執行 console
確認數值為何?
const arr = [1, 2, 3, 4, 5];
const reduceArr = arr.reduce((accumulator, currentValue) => {
console.log(accumulator); // 1, 3, 6, 10
console.log(currentValue); // 2, 3, 4, 5
return accumulator + currentValue
});
可以清楚看到 accumulator
是從 1 開始接收 currentValue
的值並開始累計,而 currentValue
就從 2 開始 loop,加總方法如以下表格:
2. 有提供 initialValue
(預設值)
const arr = [1, 2, 3, 4, 5];
const reduceArr = arr.reduce((accumulator, currentValue) => {
return accumulator + currentValue
}, 0);
console.log(reduceArr); // 15
可以看到加上預設值 0,雖然 return value
跟前面範例一樣是 15,但內部結構已不相同。這裡一樣執行 console
確認數值為何?
const arr = [1, 2, 3, 4, 5];
const reduceArr = arr.reduce((accumulator, currentValue) => {
console.log(accumulator); // 0, 1, 3, 6, 10
console.log(currentValue); // 1, 2, 3, 4, 5
return accumulator + currentValue
}, 0);
這裡的 accumulator
變成從 0 開始計算,後面接續 currentValue
的值累計。而 currentValue
會從陣列的第一個值開始 loop。
那如果預設值不為 0 而是 5,又會有什麼不同的結果呢?
const arr = [1, 2, 3, 4, 5];
const reduceArr = arr.reduce((accumulator, currentValue) => {
console.log(accumulator); // 5, 6, 8, 11, 15
console.log(currentValue); // 1, 2, 3, 4, 5
return accumulator + currentValue
}, 5);
這裡的 accumulator
變成從 5 開始計算,後面接續 currentValue
的值累計。return value
變成 20。
更多 reduce 應用
reduce
除了可以處理數字累計之外,還可以做更廣泛的應用。
合併陣列
const arr = [['a', 'b'], ['c', 'd'], ['e', 'f']];
const reduceArr = arr.reduce((accumulator, currentValue) => {
return accumulator.concat(currentValue);
}, []);
console.log(reduceArr); // ['a', 'b', 'c', 'd', 'e', 'f'];
可以看到當預設值為空陣列時,reduce
如何透過累計的方式 return 一個合併後的新陣列。
計算相同字串的數量並以物件呈現
const arr = ['a', 'a', 'b', 'c', 'c', 'c', 'e'];
const reduceArr = arr.reduce((accumulator, currentValue) => {
if(accumulator[currentValue]) {
accumulator[currentValue] ++;
} else {
accumulator[currentValue] = 1;
}
return accumulator;
}, {});
console.log(reduceArr); // { a: 2, b: 1, c: 3, e: 1 }
這一段主要在計算變數 arr
內值出現的次數。當預設值為一空物件時,reduce
loop 過變數 arr
後會將 currentValue
累計在已繼承預設值的 accumulator
上並產生一個新物件。
如果用一般的陣列方法可以這麼寫(以 forEach
為例)
const arr = ['a', 'a', 'b', 'c', 'c', 'c', 'e'];
const obj = {};
arr.forEach(function(char) {
if(obj[char]) {
obj[char] ++;
} else {
obj[char] = 1;
}
})
console.log(obj) // { a: 2, b: 1, c: 3, e: 1 }
也可以達到一樣的效果。
其實用這兩種方法的邏輯是一致的,但 reduce
的 initialValue
可以設為任一型態(數值、物件、陣列),而 accumulator
可繼承 initialValue
的型態並接收 currentValue
計算後的結果。因此雖然程式碼看似沒有簡化很多,但若需要對陣列操作進行比較細膩的比對與計算的話寫起來會較直覺且靈活一些。
總結
- 在執行
reduce
時需先確認有沒有放initialValue
,因為這會直接影響 return 的結果。 accumulator
(累計值)是一個特殊的存在,它會反應currentValue
的累計值,這在其他陣列方法比較少見。- 承上,因為有
accumulator
這個參數,使reduce
可以針對陣列的 item 與 item 之間執行一些比較細膩的比對與操作。