以前我沒得選,現(xiàn)在我只想用 Array.prototype.reduce
前言
對于新手來說, reduce 沒有 map、 forEach、 filter 等數(shù)組方法那么友好。但是不得不說,它們能干的事情, reduce 一個不落下,直呼“B神”。
我心目中 reduce 的形象。
寫這篇文章的目的就是想給大家好好的介紹一下 reduce 它有多“騷”。
語法
reduce 接收 2 個參數(shù):第一個參數(shù)是回調(diào)函數(shù)(必選),第二個參數(shù)是初始值 initialValue(可選) 。而第一個參數(shù)(回調(diào)函數(shù)),接收下面四個參數(shù):
Accumulator (acc) (累計(jì)器) Current Value (cur) (當(dāng)前值) Current Index (idx) (當(dāng)前索引) Source Array (src) (源數(shù)組)
我們平時在業(yè)務(wù)中多數(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í)行就像一個貪吃蛇,蛇每吃一個豆子,豆子將會變成蛇身的一部分,蛇再去吃下一個豆子。 
重塑
它的能力遠(yuǎn)不止于此,它就像神奇寶貝里的“百變怪”,想要變成什么就變成什么。 
reduce -> map
map 方法接收一個回調(diào)函數(shù),函數(shù)內(nèi)接收三個參數(shù),當(dāng)前項(xiàng)、索引、原數(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 方法,接收一個回調(diào)函數(shù) callback 作為參數(shù)(就是 map 傳入的回調(diào)函數(shù)),內(nèi)部通過 this 拿到當(dāng)前需要操作的數(shù)組,這里 reduce 方法的第二個參數(shù)初始值很關(guān)鍵,需要設(shè)置成一個 [] ,這樣便于后面把操作完的單項(xiàng)塞入 acc 。我們需要給 callback 方法傳入三個值,當(dāng)前項(xiàng)、索引、原數(shù)組,也就是原生 map 回調(diào)函數(shù)能拿到的值。返回 item 塞進(jìn) acc,并且返回 acc ,作為下一個循環(huán)的 acc(貪吃蛇原理)。最終 this.reduce 返回了新的數(shù)組,并且長度不變。
reduce -> forEach
forEach 接收一個回調(diào)函數(shù)作為參數(shù),函數(shù)內(nèi)接收四個參數(shù)當(dāng)前項(xiàng)、索引、原函數(shù)、當(dāng)執(zhí)行回調(diào)函數(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 ,轉(zhuǎn) forEach 只是改改結(jié)構(gòu)的問題。
reduce -> filter
filter 同樣接收一個回調(diào)函數(shù),回調(diào)函數(shù)返回 true 則返回當(dāng)前項(xiàng),反之則不返回?;卣{(diào)函數(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ù)項(xiàng)。
// [2, 4]
filter 方法中 callback 返回的是 Boolean 類型,所以通過 if 判斷是否要塞入累計(jì)器 acc ,并且返回 acc 給下一次對比。最終返回整個過濾后的數(shù)組。
reduce -> find
find 方法中 callback 同樣也是返回 Boolean 類型,返回你要找的第一個符合要求的項(xiàng)。
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,代表沒有找到想要的項(xiàng),則 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 的值,找到第一個符合判斷標(biāo)準(zhǔn)的值就不再進(jìn)行賦值操作。
諸如此類的操作,大家可以自己練習(xí),就當(dāng)是再熟悉一遍 Array 數(shù)組方法。
衍生
同學(xué)們可能在業(yè)務(wù)中,對 reduce 使用頻率并不高,那是因?yàn)槟銢]有很好的去理解它。在很多業(yè)務(wù)場景下,你用了更麻煩、更冗余的代碼完成了你的需求。接下帶大家學(xué)習(xí)一些用 reduce 能解決的問題。
將二維數(shù)組轉(zhuǎn)為一維數(shù)組
const testArr = [[1,2], [3,4], [5,6]]
testArr.reduce((acc, cur) => {
return acc.concat(cur)
}, [])
// [1,2,3,4,5,6]
計(jì)算數(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 判斷累計(jì)器 acc 中是否含有 cur 屬性,如果沒有默認(rèn)賦值 1,如果已經(jīng)存在 += 1 累加一次。在實(shí)際的開發(fā)業(yè)務(wù)中,這個方法非常常用,變種也很多。比如給你一個賬單列表(項(xiàng)與項(xiàng)之間的消費(fèi)類型有相同情況),讓你統(tǒng)計(jì)賬單列表中各個消費(fèi)類型的支出情況,如 購物 、 學(xué)習(xí) 、 轉(zhuǎn)賬 等消費(fèi)類型的支出情況。這就用到了上述方法,去通過歸類。
按屬性給數(shù)組分類
什么叫按照屬性給數(shù)組分類,其實(shí)就是給定一個依據(jù),把符合條件的歸并到一起。再拿賬單舉例,就是按各個消費(fèi)類型歸為一類。
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è)置它賦值 [] 空數(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ì)器里是否已經(jīng)有當(dāng)前項(xiàng)。
求最大值或最小值
一個對象數(shù)組內(nèi),我想拿到某一項(xiàng)里某個屬性最大或者最小的那一項(xiàng)。
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 ,后面進(jìn)入對比判斷,如果 acc 的 age 屬性小于 cur 的 age 屬性,重制 acc 。相等的話默認(rèn)返回 acc 。
總結(jié)
reduce 的使用場景還有非常多,當(dāng)你遇到數(shù)組復(fù)雜操作的時候,就是它大顯身手的時候。深入研究它,對你今后的業(yè)務(wù)開發(fā)及面試都有很大的幫助。
點(diǎn)擊閱讀原文,快來加入我們吧!
