Eden Pan 已發佈 2019-11-28

JavaScript reduce 在做什麼?

陣列方法有很多種,包括 forEachmapfilter 等等,其中 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 方法跟其他陣列方法(例如:mapfilter)的差別是它會 return 一個值,而不是一個新陣列,這會連帶使 reduce 的語法結構跟邏輯與其他方法不太相同。

reduce 的基礎語法

Array.reduce(callback[accumulator, currentValue, currentIndex, array], initialValue)

看起來有點複雜,其實就是以下這五個參數:

  • accumulator:經由個別 currentValue 加總的累計值
  • currentValue:Array 的個別 item
  • currentIndex:Array item 的索引
  • array:呼叫該 Array method 的陣列
  • initialValue:預設值,放在 function 的最後方,非必填

其中比較特別的是 accumulatorinitialValue,在其他的陣列方法裡也少見。用文字敘述可能會不太清楚這些參數的意思,這裡用最簡單的陣列數值加總為例,並預設兩種不同狀況:

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,加總方法如以下表格:

images

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。

images

那如果預設值不為 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。

images

更多 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 }

也可以達到一樣的效果。

其實用這兩種方法的邏輯是一致的,但 reduceinitialValue 可以設為任一型態(數值、物件、陣列),而 accumulator 可繼承 initialValue 的型態並接收 currentValue 計算後的結果。因此雖然程式碼看似沒有簡化很多,但若需要對陣列操作進行比較細膩的比對與計算的話寫起來會較直覺且靈活一些。

總結

  • 在執行 reduce 時需先確認有沒有放 initialValue,因為這會直接影響 return 的結果。
  • accumulator(累計值)是一個特殊的存在,它會反應 currentValue 的累計值,這在其他陣列方法比較少見。
  • 承上,因為有 accumulator 這個參數,使 reduce 可以針對陣列的 item 與 item 之間執行一些比較細膩的比對與操作。

參考文章

關於筆者

暱稱:Eden Pan

介紹:前端工程師 - 遠距工作者 - 奶爸 我的 Medium => https://medium.com/@edenpan

文章列表 文章列表