本篇是筆者深入理解reduce的一篇筆記,希望看完在項(xiàng)目中有所思考和幫助。
reduce
reduce() 方法對(duì)數(shù)組中的每個(gè)元素按序執(zhí)行一個(gè)由您提供的 reducer 函數(shù),每一次運(yùn)行 reducer 會(huì)將先前元素的計(jì)算結(jié)果作為參數(shù)傳入,最后將其結(jié)果匯總為單個(gè)返回值。,這是官方MDN上給的一段話。
每次將會(huì)把前一次的計(jì)算結(jié)果當(dāng)成下次的參數(shù)傳入,什么意思?
我們看一下簡(jiǎn)單的例子;
const sum = (arr) => {
return arr.reduce((prev, cur) => {
return prev + cur
}, 0)
}
console.log('sum: ', sum([1,2,3,4,5])) // 15
結(jié)果是15,嘿,這個(gè)sum就是這么簡(jiǎn)單嗎?我們看下之前是怎么寫的;
const sum2 = (arr) => {
let ret = 0;
arr.forEach(val => {
ret+=val;
})
return ret
}
console.log('sum2:', sum2([1,2,3,4,5])) // 15
我們發(fā)現(xiàn)在之前我們的做法是循環(huán)計(jì)算,reduce的方式比循環(huán)方式代碼要簡(jiǎn)單得多,但是并不是像循環(huán)方式一樣那么通俗易懂,具體我們斷點(diǎn)分析一下;
const sum = (arr) => {
return arr.reduce((prev, cur) => {
debugger;
return prev + cur
}, 0)
}
console.log('sum: ', sum([1,2,3,4,5])) // 15
首次:

其實(shí)我們發(fā)現(xiàn),?reduce?回調(diào)函數(shù)內(nèi),第一個(gè)參數(shù)prev?默認(rèn)就是初始值傳入的0?,然后cur就是每次循環(huán)數(shù)組的當(dāng)前值。
第一次:prev:0, cur: 1,執(zhí)行返回結(jié)果0+1,為第二次循環(huán)的初始值prev:1。
第二次:prev:1, cur:2,執(zhí)行返回結(jié)果1+2,為第三次循環(huán)的初始值prev:3。
...
第五次:prev:10, cur:5,執(zhí)行返回結(jié)果10+5,結(jié)束。
所以我們始終記住這個(gè)萬能公式就行,prev首次是默認(rèn)傳入的值,當(dāng)循環(huán)迭代下一次循環(huán)時(shí),會(huì)將上一次返回的結(jié)果作為prev,cur永遠(yuǎn)是當(dāng)前迭代的item。
var arr = [];
const callback = (prev, current, currentIndex, source) => {
// 首次prev = init, 后面每次計(jì)算后結(jié)果作為下一次的prev,current是當(dāng)前arr的item
// current: 當(dāng)前的數(shù)組的item
// currentIndex: 當(dāng)前索引
// source 原數(shù)組,也是arr
}
arr.reduce(callback, init?)
注意init是可選的,如果有值,則prev默認(rèn)取它,那么current就取默認(rèn)第一個(gè)值,如果init沒有值,那么prev就是第一個(gè),current就是第二值,你會(huì)發(fā)現(xiàn)不給默認(rèn)值,比給默認(rèn)值少了一次循環(huán)。
const sum = (arr) => {
return arr.reduce((prev, cur, curentIndex, arr) => {
console.log(prev, cur, curentIndex, arr)
return prev + cur
})
}
console.log('sum: ',sum([1,2,3,4,5])) // 15
// 1 2 1 [1, 2, 3, 4, 5]
// 3 3 2 [1, 2, 3, 4, 5]
// 6 4 3 [1, 2, 3, 4, 5]
// 10 5 4 [1, 2, 3, 4, 5]
過濾數(shù)據(jù)中指定字段數(shù)據(jù);
用reduce過濾指定需要的字段;
let sourceArr = [
{id: 1, name: 'Web技術(shù)學(xué)苑', age: 18},
{id: 2, name: 'Maic', age: 20},
{id: 3, name: 'Tom', age: 16},
]
const ret = sourceArr.reduce((prev, cur) => {
const {id, age} = cur;
return prev.concat({id, age})
}, [])
console.log(ret);
// [ { id: 1, age: 18 }, { id: 2, age: 20 }, { id: 3, age: 16 } ]
如果是用map大概就是下面這樣的了。
...
const ret2 = sourceArr.map(v => {
return { id: v.id, age: v.age }
})
console.log('ret2', ret2);
多維數(shù)組打平,二維轉(zhuǎn)一維;
reduce是下面這樣的;
const sourceArr2 = [[1,2,3], [4,5,6], [8,9], 0]
const ret3 = sourceArr2.reduce((prev, cur) => {
return prev.concat(cur)
}, [])
以前你可能會(huì)這樣的;
...
const ret4 = sourceArr2.flat(1)
或者用遞歸方式;
var flatLoop = (source, ret = []) => {
const loop = (arr) => {
arr.forEach(v => {
if (Array.isArray(v)) {
loop(v)
} else {
ret.push(v)
}
})
}
loop(source)
return ret
}
flatLoop(sourceArr2, [])
統(tǒng)計(jì)一個(gè)字符出現(xiàn)的次數(shù);
forEach版本;
const strCount = (arr) => {
const obj = {}
arr.forEach(key => {
if (key in obj) {
obj[key]+=1;
} else {
obj[key]=1;
}
});
return obj
}
const ret5 = strCount(['a', 'a', 'b', 'c', 'd'])
console.log('ret5', ret5)
// ret5 {a: 2, b: 1, c: 1, d: 1}
reduce版本實(shí)現(xiàn);
const strCount2 = (arr) => {
return arr.reduce((prev, cur) => {
if (cur in prev) {
prev[cur]+=1;
} else {
prev[cur] = 1;
}
return prev
}, {})
}
console.log('ret6', strCount2(['a', 'a', 'b', 'c', 'd']))
獲取數(shù)組中某個(gè)字段的所有集合;
var publicInfo = [
{
id: '1',
name: 'Web技術(shù)學(xué)苑',
age: 8
},
{
id: '2',
name: '前端從進(jìn)階到入院',
age: 10
},
{
id: '3',
name: '前端之神',
age: 15
},
{
id: '3',
name: '前端之巔',
age: 12
}
]
const ret7 = publicInfo.map(v => v.name)
console.log('ret7', ret7)
reduce實(shí)現(xiàn);
const ret8 = publicInfo.reduce((prev, cur) => {
return prev.concat(cur.name)
}, [])
console.log('ret8', ret8)
數(shù)據(jù)去重;
以前你可以用Set或者循環(huán)去做的
const sourceData = ['1','1', '2', 3,4,5,3]
console.log([...new Set(sourceData)]) // ['1','2',3,4,5]
// or
const obj = {}
sourceData.forEach(item => {
obj[item] = item
})
console.log(Object.values(obj))
reduce實(shí)現(xiàn)去重。
...
consy ret9 = sourceData.reduce((prev, cur) => {
if (prev.indexOf(cur) === -1) {
prev.push(cur)
}
return prev
}, [])
代替filter與map
假設(shè)我們有一個(gè)場(chǎng)景,就是在原數(shù)據(jù)中過濾找出age>10大于的數(shù)據(jù)并返回對(duì)應(yīng)的name。
var publicInfo = [
{
id: '1',
name: 'Web技術(shù)學(xué)苑',
age: 10
},
{
id: '2',
name: '前端從進(jìn)階到入院',
age: 10
},
{
id: '3',
name: '前端之神',
age: 12
},
{
id: '3',
name: '前端之巔',
age: 12
}
]
const ret11 = publicInfo.filter(v => v.age >10).map(v => v.name);
console.log(ret11); // ['前端之神', '前端之巔']
我們知道上面使用filter與map有兩次循環(huán),但是reduce就可以做到僅一次循環(huán)就可以搞定。
...
publicInfo.reduce((prev, cur) => {
if (cur.age > 10) {
prev.push(cur.name)
}
return prev
}, [])
關(guān)于reduce[1]更多的實(shí)踐可以參考MDN文檔,在項(xiàng)目中更多的實(shí)踐以后再一一補(bǔ)充。
總結(jié)
主要分析了reduce這個(gè)計(jì)算方法特性,每次計(jì)算的結(jié)果會(huì)當(dāng)成下一次的prev的初始值,第二個(gè)參數(shù)``cur`是當(dāng)前循環(huán)數(shù)組的值。
如果reduce給了初始值,那么prev是就是當(dāng)前傳入的初始值,如果沒有初始值,則默認(rèn)就是當(dāng)前數(shù)組的首項(xiàng),cur就是第二元素,默認(rèn)沒有初始值會(huì)比給初始值少一次循環(huán)。
以reduce實(shí)踐了一些例子,夯實(shí)reduce的一些用法特性。
本文示例源碼code example[2]。
參考資料
[1]reduce: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
[2]code example: https://github.com/maicFir/lessonNote/tree/master/javascript/22-reduce