在之前笔者总结过一篇关于数组盘盘项目中你常用到的数组API的文章,在这篇文章中有粗略的讲解到reduce
这个计算API
,关于reduce
这个API
印证了那句,会者不难,难者不会
,因为reduce
实现的功能虽然简单,但是代码看起来就没有其他API
容易理解。
本篇是笔者深入理解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]