【JavaScript】以前我沒得選,現(xiàn)在我只想用 Array.prototype.reduce
前言
對于新手來說, reduce 沒有 map、 forEach、 filter 等數(shù)組方法那么友好。但是不得不說,它們能干的事情, reduce 一個不落下,直呼“B神”。
我心目中 reduce 的形象。
寫這篇文章的目的就是想給大家好好的介紹一下 reduce 它有多“騷”。
語法
reduce 接收 2 個參數(shù):第一個參數(shù)是回調函數(shù)(必選),第二個參數(shù)是初始值 initialValue(可選) 。而第一個參數(shù)(回調函數(shù)),接收下面四個參數(shù):
Accumulator (acc) (累計器) Current Value (cur) (當前值) Current Index (idx) (當前索引) Source Array (src) (源數(shù)組)
我們平時在業(yè)務中多數(shù)使用到的是前兩個參數(shù),并且有以下兩種情況。
瀏覽器支持情況

圖片來源:caniuse.com/
不帶初始值
[1,2,3,4].reduce((acc, cur) => {
return acc + cur
})
// 1 + 2 + 3 + 4
// 10
帶初始值
[1,2,3,4].reduce((acc, cur) => {
return acc + cur
}, 10)
// 10 + 1 + 2 + 3 + 4
// 20
?? 初始值 initialValue 可以是任意類型。如果沒有提供 initialValue,reduce 會從索引 1 的地方開始執(zhí)行 callback 方法,跳過第一個索引。如果提供 initialValue,從索引 0 開始。它的執(zhí)行就像一個貪吃蛇,蛇每吃一個豆子,豆子將會變成蛇身的一部分,蛇再去吃下一個豆子。 
重塑
它的能力遠不止于此,它就像神奇寶貝里的“百變怪”,想要變成什么就變成什么。 
reduce -> map
map 方法接收一個回調函數(shù),函數(shù)內接收三個參數(shù),當前項、索引、原數(shù)組,返回一個新的數(shù)組,并且數(shù)組長度不變。知道了這些特征之后,我們用 reduce 重塑 map 。
const testArr = [1, 2, 3, 4]
Array.prototype.reduceMap = function(callback) {
return this.reduce((acc, cur, index, array) => {
const item = callback(cur, index, array)
acc.push(item)
return acc
}, [])
}
testArr.reduceMap((item, index) => {
return item + index
})
// [1, 3, 5, 7]
在 Array 的原型鏈上添加 reduceMap 方法,接收一個回調函數(shù) callback 作為參數(shù)(就是 map 傳入的回調函數(shù)),內部通過 this 拿到當前需要操作的數(shù)組,這里 reduce 方法的第二個參數(shù)初始值很關鍵,需要設置成一個 [] ,這樣便于后面把操作完的單項塞入 acc 。我們需要給 callback 方法傳入三個值,當前項、索引、原數(shù)組,也就是原生 map 回調函數(shù)能拿到的值。返回 item 塞進 acc,并且返回 acc ,作為下一個循環(huán)的 acc(貪吃蛇原理)。最終 this.reduce 返回了新的數(shù)組,并且長度不變。
reduce -> forEach
forEach 接收一個回調函數(shù)作為參數(shù),函數(shù)內接收四個參數(shù)當前項、索引、原函數(shù)、當執(zhí)行回調函數(shù) callback 時,用作 this 的值,并且不返回值。
const testArr = [1, 2, 3, 4]
Array.prototype.reduceForEach = function(callback) {
this.reduce((acc, cur, index, array) => {
callback(cur, index, array)
}, [])
}
testArr.reduceForEach((item, index, array) => {
console.log(item, index)
})
// 1234
// 0123
只要看得懂 reduce -> map ,轉 forEach 只是改改結構的問題。
reduce -> filter
filter 同樣接收一個回調函數(shù),回調函數(shù)返回 true 則返回當前項,反之則不返回?;卣{函數(shù)接收的參數(shù)同 forEach 。
const testArr = [1, 2, 3, 4]
Array.prototype.reduceFilter = function (callback) {
return this.reduce((acc, cur, index, array) => {
if (callback(cur, index, array)) {
acc.push(cur)
}
return acc
}, [])
}
testArr.reduceFilter(item => item % 2 == 0) // 過濾出偶數(shù)項。
// [2, 4]
filter 方法中 callback 返回的是 Boolean 類型,所以通過 if 判斷是否要塞入累計器 acc ,并且返回 acc 給下一次對比。最終返回整個過濾后的數(shù)組。
reduce -> find
find 方法中 callback 同樣也是返回 Boolean 類型,返回你要找的第一個符合要求的項。
const testArr = [1, 2, 3, 4]
const testObj = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }]
Array.prototype.reduceFind = function (callback) {
return this.reduce((acc, cur, index, array) => {
if (callback(cur, index, array)) {
if (acc instanceof Array && acc.length == 0) {
acc = cur
}
}
// 循環(huán)到最后若 acc 還是數(shù)組,且長度為 0,代表沒有找到想要的項,則 acc = undefined
if ((index == array.length - 1) && acc instanceof Array && acc.length == 0) {
acc = undefined
}
return acc
}, [])
}
testArr.reduceFind(item => item % 2 == 0) // 2
testObj.reduceFind(item => item.a % 2 == 0) // {a: 2}
testObj.reduceFind(item => item.a % 9 == 0) // undefined
你不知道操作的數(shù)組是對象數(shù)組還是普通數(shù)組,所以這里只能直接覆蓋 acc 的值,找到第一個符合判斷標準的值就不再進行賦值操作。
諸如此類的操作,大家可以自己練習,就當是再熟悉一遍 Array 數(shù)組方法。
衍生
同學們可能在業(yè)務中,對 reduce 使用頻率并不高,那是因為你沒有很好的去理解它。在很多業(yè)務場景下,你用了更麻煩、更冗余的代碼完成了你的需求。接下帶大家學習一些用 reduce 能解決的問題。
將二維數(shù)組轉為一維數(shù)組
const testArr = [[1,2], [3,4], [5,6]]
testArr.reduce((acc, cur) => {
return acc.concat(cur)
}, [])
// [1,2,3,4,5,6]
計算數(shù)組中每個元素出現(xiàn)的個數(shù)
const testArr = [1, 3, 4, 1, 3, 2, 9, 8, 5, 3, 2, 0, 12, 10]
testArr.reduce((acc, cur) => {
if (!(cur in acc)) {
acc[cur] = 1
} else {
acc[cur] += 1
}
return acc
}, {})
// {0: 1, 1: 2, 2: 2, 3: 3, 4: 1, 5: 1, 8: 1, 9: 1, 10: 1, 12: 1}
這里注意,我初始化的值變成了 {} ,這個需求需要鍵值對的形式,利用 cur in acc 判斷累計器 acc 中是否含有 cur 屬性,如果沒有默認賦值 1,如果已經(jīng)存在 += 1 累加一次。在實際的開發(fā)業(yè)務中,這個方法非常常用,變種也很多。比如給你一個賬單列表(項與項之間的消費類型有相同情況),讓你統(tǒng)計賬單列表中各個消費類型的支出情況,如 購物 、 學習 、 轉賬 等消費類型的支出情況。這就用到了上述方法,去通過歸類。
按屬性給數(shù)組分類
什么叫按照屬性給數(shù)組分類,其實就是給定一個依據(jù),把符合條件的歸并到一起。再拿賬單舉例,就是按各個消費類型歸為一類。
const bills = [
{ type: 'shop', momey: 223 },
{ type: 'study', momey: 341 },
{ type: 'shop', momey: 821 },
{ type: 'transfer', momey: 821 },
{ type: 'study', momey: 821 }
];
bills.reduce((acc, cur) => {
// 如果不存在這個鍵,則設置它賦值 [] 空數(shù)組
if (!acc[cur.type]) {
acc[cur.type] = [];
}
acc[cur.type].push(cur)
return acc
}, {})

數(shù)組去重
這個就不解釋了,直接上代碼。
const testArr = [1,2,2,3,4,4,5,5,5,6,7]
testArr.reduce((acc, cur) => {
if (!(acc.includes(cur))) {
acc.push(cur)
}
return acc
}, [])
// [1, 2, 3, 4, 5, 6, 7]
上述代碼邏輯,就是逐一對比,通過 includes 方法檢查累計器里是否已經(jīng)有當前項。
求最大值或最小值
一個對象數(shù)組內,我想拿到某一項里某個屬性最大或者最小的那一項。
const testArr = [
{ age: 20 },
{ age: 21 },
{ age: 22 }
]
testArr.reduce((acc, cur) => {
if (!acc) {
acc = cur
return acc
}
if (acc.age < cur.age) {
acc = cur
return acc
}
return acc
}, 0)
// {age: 22}
第一次沒有對比直接 acc 賦值 cur ,后面進入對比判斷,如果 acc 的 age 屬性小于 cur 的 age 屬性,重制 acc 。相等的話默認返回 acc 。
總結
reduce 的使用場景還有非常多,當你遇到數(shù)組復雜操作的時候,就是它大顯身手的時候。深入研究它,對你今后的業(yè)務開發(fā)及面試都有很大的幫助。
