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

其實我們發現,?reduce?回調函數內,第一個參數prev?默認就是初始值傳入的0?,然后cur就是每次循環數組的當前值。
第一次:prev:0, cur: 1,執行返回結果0+1,為第二次循環的初始值prev:1。
第二次:prev:1, cur:2,執行返回結果1+2,為第三次循環的初始值prev:3。
...
第五次:prev:10, cur:5,執行返回結果10+5,結束。
所以我們始終記住這個萬能公式就行,prev首次是默認傳入的值,當循環迭代下一次循環時,會將上一次返回的結果作為prev,cur永遠是當前迭代的item。
var arr = [];
const callback = (prev, current, currentIndex, source) => {
// 首次prev = init, 后面每次計算后結果作為下一次的prev,current是當前arr的item
// current: 當前的數組的item
// currentIndex: 當前索引
// source 原數組,也是arr
}
arr.reduce(callback, init?)
注意init是可選的,如果有值,則prev默認取它,那么current就取默認第一個值,如果init沒有值,那么prev就是第一個,current就是第二值,你會發現不給默認值,比給默認值少了一次循環。
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]
過濾數據中指定字段數據;
用reduce過濾指定需要的字段;
let sourceArr = [
{id: 1, name: 'Web技術學苑', 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);
多維數組打平,二維轉一維;
reduce是下面這樣的;
const sourceArr2 = [[1,2,3], [4,5,6], [8,9], 0]
const ret3 = sourceArr2.reduce((prev, cur) => {
return prev.concat(cur)
}, [])
以前你可能會這樣的;
...
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, [])
統計一個字符出現的次數;
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版本實現;
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']))
獲取數組中某個字段的所有集合;
var publicInfo = [
{
id: '1',
name: 'Web技術學苑',
age: 8
},
{
id: '2',
name: '前端從進階到入院',
age: 10
},
{
id: '3',
name: '前端之神',
age: 15
},
{
id: '3',
name: '前端之巔',
age: 12
}
]
const ret7 = publicInfo.map(v => v.name)
console.log('ret7', ret7)
reduce實現;
const ret8 = publicInfo.reduce((prev, cur) => {
return prev.concat(cur.name)
}, [])
console.log('ret8', ret8)
數據去重;
以前你可以用Set或者循環去做的
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實現去重。
...
consy ret9 = sourceData.reduce((prev, cur) => {
if (prev.indexOf(cur) === -1) {
prev.push(cur)
}
return prev
}, [])
代替filter與map
假設我們有一個場景,就是在原數據中過濾找出age>10大于的數據并返回對應的name。
var publicInfo = [
{
id: '1',
name: 'Web技術學苑',
age: 10
},
{
id: '2',
name: '前端從進階到入院',
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有兩次循環,但是reduce就可以做到僅一次循環就可以搞定。
...
publicInfo.reduce((prev, cur) => {
if (cur.age > 10) {
prev.push(cur.name)
}
return prev
}, [])
關于reduce[1]更多的實踐可以參考MDN文檔,在項目中更多的實踐以后再一一補充。
總結
主要分析了reduce這個計算方法特性,每次計算的結果會當成下一次的prev的初始值,第二個參數``cur`是當前循環數組的值。
如果reduce給了初始值,那么prev是就是當前傳入的初始值,如果沒有初始值,則默認就是當前數組的首項,cur就是第二元素,默認沒有初始值會比給初始值少一次循環。
以reduce實踐了一些例子,夯實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