我是阿傑,曾經聽雪瑞學姐說過 reduce() 好好用、整理 API 好棒棒,我當下心想這麼好用的東西要是不會用可是要吃大虧了呢 (大媽心態??),貪小便宜的我立刻打開 MDN 搜尋這個咩色並帶著不知哪來的自信閱讀了起來,在過了約莫 10 分鐘後,我突感一陣暈眩並當機立斷地把分頁關掉,接著就乖乖地帶著電腦走去跟學姐跪求教學!
那我們今天就來介紹一下這個新人殺手 - Array.prototype.reduce,不要擔心太多,我們勇敢地往下看...

我們會分成以下幾個部分介紹:
當你需要把一個陣列整理成任一型態的值,即可考慮使用,什麼意思呢?我們可以把 reduce 想像成逐漸變成某個東西,也就是我們可以把一個陣列逐漸變成下列的結果之一:
最常聽到的例子可能就是把一個包含數字的陣列整理成一個數字,例如"加總":
[1,2,3,4,5] => 15
你可以選擇是否給這個 reduce() 一個 初始值 (initial value),這會影響它的行為模式跟結果。
reduce((previousValue, currentValue, currentIndex, array) => { * ... *}, initialValue)
reduce(callbackFn, initialValue)
reduce(function(previousValue, currentValue, currentIndex, array){ * ... * }, initialValue)
reduce() 的第一個參數是 callback, 第 2 個參數為選擇性的 (optional) initialValue。
callback這個 callback 又被稱為 reducer ,reduce 會按照陣列元素的順序依次呼叫這個 callback,但被呼叫的最大次數會因是否有提供 initialValue 而不同,換句話說,如果陣列有 5 個元素,那這個 callback 最多會被呼叫 5 次。
callback 每次都會被帶入 4 個參數呼叫,分別是 previousValue、currentValue、currentIndex 及 array。
callback 每次都必須回傳一個值出去,這個值會成為下個 callback 的 previousValue,直到陣列被遍歷完畢。
previousValue
前一次 callback 回傳的值,又稱為 accumulator (累加器)。
如果有提供 initialValue,那第 1 次 callback 的 previousValue 會是 initialValue;否則會是陣列的第 1 個元素 (array[0])。
currentValue
陣列當前的元素。
如果有提供 initialValue,那第 1 次 callback 的 currentValue 會是陣列的第 1 個元素 (array[0]);否則會是陣列的第 2 個元素 (array[1])。
currentIndex
陣列當前元素的索引 (index).
如果有提供 initialValue ,那第 1 次 callback 的 currentIndex 會是 0;否則會是 1
array
呼叫 reduce() 的陣列本身  (被遍歷的陣列本身),不論 reduce() 當下遍歷到哪個元素。
initialValue (可選的)它會影響第 1 次 callback 帶入的參數,如果有提供 initialValue :
previousValue 會是 initialValue
currentValue 會是陣列的第 1 個元素 (array[0])currentIndex 會是 0如果沒有提供 initialValue:
previousValue 會是陣列的第 1 個元素 (array[0])currentValue 會是陣列的第 2 個元素 (array[1])currentIndex 會是 1回傳一個被 callback (reducer) 整理過的值,這個值可以是任何型態,例如數字或物件等...。
不會變動到原陣列。
要弄懂 reduce() 就必須要清楚知道 initialValue 、previousValue 跟 currentValue 之間的關係。
可以把 previousValue  看做一個累加器 (accumulator),這個累加器可以是任何值 (例如一個數字或一個物件),它會持續變化 (reduce) 並不斷地被往下傳遞,也就表示我們回傳出去的值會變成下次 callback 的 previousValue。
要注意 previousValue 除了會不斷地變化外,它跟 currentValue 及 currntIndex 的初始值都會受到 initialValue 的影響 (可以參考上面的參數說明),我們使用範例的 Example 1、Example 2 來說明參數帶入的狀況:
initialValue 時:
最後的回傳值 15 會被 reduce() 回傳出去
initialValue 時:
最後的回傳值 14 會被 reduce() 回傳出去
只要能掌握這幾個參數的變化,redue 不只可以拿來做加總之類的用途,它還可以用來整理這個陣列,讓其變成我們想要的型態,例如範例的 Example 3,我們使用了一個空陣列作為 initialValue,它便會成為第 1  次 callback 的 previousValue ,之後被不斷地傳遞並變化成我們想要的結果。
initialValue)const numbers = [1, 2, 3, 4, 5]
const sum = numbers.reduce((currentSum, currentNumber) => currentSum + currentNumber)
console.log(sum)
// 15
currentSum 為這個 reduce() 的累加器 (accumulator),它的初始值為 numbers 的第 1 個元素 - 1,而currentNumber 會從 numbers 的第 2 個元素開始 - 2。
因此 reduce 會執行 4 次callback,而每次 callback 回傳的值都會成為下一次的 currentSum。
initialValue)const numbers = [1, 2, 3, 4, 5]
const sum = numbers.reduce((currentSum, currentNumber) => currentSum + currentNumber, -1)
console.log(sum)
// 14
currentSum 為這個 reduce() 的累加器 (accumulator),它的初始值為 initialValue  - -1,而currentNumber 會從 numbers 的第 1 個元素開始 - 1。
因此 reduce 會執行 5 次callback,而每次 callback 回傳的值都會成為下一次的 currentSum。
const datas = [
	{ name: 'Alejo', ages: 21 },
	{ name: 'Emma', ages: 32 },
	{ name: 'Pedro', ages: 42 },
	{ name: 'Samantha', ages: 18 },
]
const ages = datas.reduce((ages, data) => {
	const agesObj = { [data.name]: data.ages }
	ages.push(agesObj)
	return ages
},[])
console.log(ages)
// [
//   { Alejo: 21 },
//   { Emma: 32 },
//   { Pedro: 42 },
//   { Samantha: 18 }
// ]
這個 initialValue 為一個空陣列,代表這個累加器會是一個陣列,而最後 reduce 回傳的也會是一個陣列。
const people = [
	{ name: 'Emma', gender: 'female' },
	{ name: 'Pedro', gender: 'trans' },
	{ name: 'Collin', gender: 'male' },
	{ name: 'Ted', gender: 'male' },
	{ name: 'Ginger', gender: 'trans' }
]
function groupBy(objArray, property) {
	return objArray.reduce((groupedObj, obj) => {
		const key = obj[property]
		const currentGroup = groupedObj[key] ?? []
		return { ...groupedObj, [key]: [...currentGroup, obj] }
	}, {})
}
const peopleByGender = groupBy(people, 'gender')
console.log(peopleByGender)
// {
// 	female: [{ name: 'Emma', gender: 'female'}],
// 	male: [
// 		{ name: 'Collin', gender: 'male' },
// 		{ name: 'Ted', gender: 'male' }
// 	],
// 	trans: [
// 		{ name: 'Pedro', gender: 'trans' },
// 		{ name: 'Ginger', gender: 'trans' }
// 	]
// }
這個 groupBy() 會利用指定的屬性對包含物件的陣列做分類,最後回傳一個物件,其屬性都是跟據指定屬性的值來做分類。
在這裡,我們使用 people 的每個物件裡的 gender 來做分類,也就是最後整理好的物件會有 female、male、trans 3 個屬性,每個屬性的值都會是一個陣列,包含著相關的物件。
請注意 callback 定義時的參數順序,依序應為 previousValue、currentValue、currentIndex、array,假設你只想使用 currentIndex 而不使用 currentValue,你仍然需在前面定義 currentValue,也可以增加一個底線以利閱讀,例如這樣:
array.reduce((previousValue, _currentValue, currentIndex) => { /* ... */ })
雖然 reduce() 很好用,但它在閱讀上並不是那麼直覺,尤其對新手來說會有點迷惑,我們可以多多利用其他咩色做到相同的事,如果沒辦法就應該讓參數的命名更加語義化來增加可讀性。
如果陣列沒有元素 (如果只有 empty slot 也會被視作沒有元素) 也沒有提供 initialValue 的話,會丟出一個 TypeError 的錯誤。
reduce() 無法跟其他可傳入 callback 的咩色一樣指定其 callback 的 this。
有一點值得注意的是,雖然 reduce() 不會變動到原陣列,但我們傳進去的 callback 卻有可能 ,而陣列元素被遍歷的範圍在第一次呼叫 callback 前就已經確立好了 (也就是 reduce() 被呼叫後但 callback 尚未被呼叫),因此有可能會發生以下的狀況:
上述這種高併發 (concurrent) 的更動會導致程式碼非常難以閱讀,非常不建議使用 (除非有特殊的情境)。
Array.prototype.reduce(callbackfn[,initialValue])

reduce() 的演算法並沒有要求呼叫它的一定要是一個陣列,可以從步驟 1 跟 Note 2 得知,為了方便解釋,這邊一律使用陣列來說明。
演算法的前 3 個步驟都是用來做一些前置處理,包括轉型、確認長度、確認參數是否為一個 function 等...。
在步驟 4 會看到當陣列長度為 0 時且又沒有提供 initialValue 時就會丟出 1 個 TypeError,驗證了上面注意事項所說。
步驟 7 會看到當有提供 initialValue 時,initialValue 會被指派給 accumulator (previousValue),驗證了上面參數的說明。
步驟 8 會看到如果沒有提供 initialValue 時,會從索引 0 開始找有無相對應的元素並指派給 accumulator,因為這邊不會是空陣列,所以第 1 個元素 (array[0]) 就會成為 accumulator 的初始值,驗證了上面的參數說明。
步驟 9 會看到遍歷的次數在 callback 第 1 次呼叫前就已經決定好了,而且每次的 callback 呼叫前都會先檢查相對應的屬性 (陣列索引) 是否存在, 如果不存在則不會呼叫,驗證了上面注意事項對高併發之 callback 的說明。
在步驟 9 還會看到 callback 被呼叫時的 this 為 undefined ,驗證了上面所說的 - 我們無法指定 callback 的 this。
我們來驗證一下 reduce() 是否如規範所說的被做成通用的一個方法:
const fakeArray = {
	0: 1,
	1: 2,
	2: 3,
	3: 4,
	length: 4
}
const sum = Array.prototype.reduce.call(fakeArray, (sum, currentNum) => sum + currentNum)
console.log(sum)
// 10
只要清楚知道 accumulator 會被如何傳遞,以及 initialValue 是如何影響這些參數的初始值, reduce() 應該就不會讓人這麼迷惑。
而使用 reduce()這類咩色還有一個很重要的地方,就是可以減少變數的宣告,不但可以提高閱讀性,還可以減少變數污染之類的問題。
最後,希望大家可以開心地使用各種咩色,體驗它帶給你的便利,祝大家歸剛沒煩惱。
特地來留一下reduce
以前新人看到reduce真的超就崩潰啦
但自從一次茅塞頓開之後
真的就是好用而已
要離職之前還故意留一堆reduce的code給後人(誤
印象中我出面試題還故意出這個,要考一下是不是真的懂