arr.reduce(callback,[initialValue])
reduce是js数组的常用方法之一,我们叫它累加函数,一般我们经常使用它来进行数组的求和:
let data = [1,2,3,4,5];
let sum = data.reduce((prev, next)=>prev+next)
console.log(sum)//15
但是它的用途只是这样的么?
那显然不是的。。。下边我们得先说说callback和initialValue具体表示的啥意思
initialValue是累加的初始值,可以赋值可也可以不赋值,不赋值的时候取数组里面的第一个值
callback是一个回调函数,它是数组中每一个元素依次执行的回调函数,接受四个参数
- prev——上一次调用函数返回的值,或者是初始值(initialValue)
- next——数组中当前被处理的元素
- index——当前元素的索引
- arr——当前调用reduce的数组,也就是data
那现在有个疑问了,initialValue值设置与不设置有啥区别呢?
当initialValue设置的时候
let sum = data.reduce((prev, next)=>prev+next, 0)
上边data.reduce里面的回调函数会执行5次,因为prev为0,第一次执行0+1
当不设置initialValue的时候
let sum = data.reduce((prev, next)=>prev+next)
上边data.reduce里面的回调函数会执行4次,因为prev为1,第一次执行1+2
OK,这就是区别!!!下边是正题
1、计算数组中每个元素出现的次数
let data1 = ['A', 'B', 'C', 'D', 'A'];
let num = data1.reduce((prev, next)=>{
//初始值给个空对象,第一次执行prev就是{},next就是A
//遍历整个数组利用mapkey唯一特性,统计出每个元素出现的次数
if(next in prev){
prev[next]++
}else{
prev[next] = 1
}
return prev
},{})
console.log(num); //{A: 2, B: 1, C: 1, D: 1}
2、数组去重复
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((prev, next)=>{
if(!prev.includes(next)){
return prev.concat(next)
}else{
return prev
}
},[])
console.log(newArr);// [1, 2, 3, 4]
有了上一个例子这里就不过多解释了,感觉上reduce就是遍历加过滤的作用,可以照顾到数组中的每个元素又可以对其中的每一项进行业务上的逻辑处理,整理成我们想要的格式,最后返回这个数据
3、将二维数组拉平
let arr1 = [[0, 1], [2, 3], [4, 5]]
let newArr1 = arr1.reduce((prev, next)=>{
return prev.concat(next)
},[])
console.log(newArr1); // [0, 1, 2, 3, 4, 5]
4、对象里的属性求和
var arr2 = [
{
name: 'a',
score: 10
},
{
name: 'b',
score: 20
},
{
name: 'c',
score: 30
}
];
var sum1 = arr2.reduce(function(prev, next) {
return next.score + prev;
}, 0);
console.log(sum1) //60
我们在工作中经常遇到上边数组对象这种格式的数据,当我们想取其中的某个数据进行操作的时候,reduce就真正的体现了它的作用了,他可以依次遍历原数组的每个值,并且对其中的值进行累加或者其他你想做的任何处理!
5、多维数组扁平化处理
let arr3 = [[[0], 1, 2], 3, [4, 5], [6, [7, 8, 9]]]
function flatarr(arr) {
return arr.reduce((prev, next)=>prev.concat(Array.isArray(next)?flatarr(next):next), [])
}
console.log(flatarr(arr3))// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
多维数组的扁平化处理也是我们面试中经常遇到的问题,以前觉得好复杂,但是现在一看是不是简单多了
6、求数组的最大值
let arr4 = [10, 20, 30];
let max = arr4.reduce((prev, next)=>Math.max(prev, next))
console.log(max) //30
感觉好想有点多余哦,这个可以使用更简单的方法
let max1 = Math.max(...arr4)
console.log(max1) //30
7、中间件
这可是一个比较难理解的点,先来一段预热
const dispatch = action => {
console.log('action', action);
return action;
}
const middleware1 = dispatch => {
return action => {
console.log("middleware1");
const result = dispatch(action);
console.log("after middleware1");
return result;
}
}
console.log(dispatch('11'))
//action 11
//11
console.log(middleware1('11'))
// action => {
// console.log("middleware1");
// const result = dispatch(action);
// console.log("after middleware1");
// return result;
// }
console.log(middleware1(dispatch)('11'))
// middleware1
// action 11
// after middleware1
// 11
第一个console很好理解,就是传个参数并且返回这个参数、第二个也不难理解返回了一段函数并且dispatch是11、第三个我们得细细查看,函数后边加了两个括号,意思就是第一次返回的是一个函数体,可以继续执行,首先传入的是一个函数,也就是dispatch等于我们传入的这个方法,return一个函数体继续执行后边的那个括号,此时action就为11了,而由于闭包dispatch方法得以保存,可以继续在下边执行,所以就先打印了middleware1,在执行dispatch方法,打印action 11,此时返回result11,在打印after middleware1,最后再返回11
是不是有点感觉了,下边来大家伙
const dispatch = action => {
console.log('action', action);
return action;
}
const middleware1 = dispatch => {
return action => {
console.log("middleware1");
const result = dispatch(action);
console.log("after middleware1");
return result;
}
}
const middleware2 = dispatch => {
return action => {
console.log("middleware2");
const result = dispatch(action);
console.log("after middleware2");
return result;
}
}
const middleware3 = dispatch => {
return action => {
console.log("middleware3");
const result = dispatch(action);
console.log("after middleware3");
return result;
}
}
//类似koa处理中间件的流程
const middlewares = [middleware1, middleware2, middleware3];
const compose = middlewares => middlewares.reduce((a, b) => args => a(b(args)))
const afterDispatch = compose(middlewares)(dispatch);
//正式执行的方法
const testAction = arg => {
return arg;
};
afterDispatch(testAction("1111"));
// middleware1
// middleware2
// middleware3
// action 1111
// after middleware3
// after middleware2
// after middleware1
上边这个流程就和koa2处理中间件的流程十分相似,类似洋葱模型,使用了reduce把依次把middlewares数组中的三个函数包起来,其中也用到了函数闭包的原理,与函数式编程的思想,最终拆开执行的是下边这样的
const dispatch = action => {
console.log('action', action);
return action;
}
const middleware1 = dispatch => {
return action => {
console.log("middleware1");
const result = action => {
console.log("middleware2");
const result = action => {
console.log("middleware3");
const result = dispatch(action);
console.log("after middleware3");
return result;
}
console.log("after middleware2");
return result;
}
console.log("after middleware1");
return result;
}
}
8、分组
const groupBy = (arr, func) => arr.map(typeof func === 'function' ? func : val => val).reduce((acc, val, i) => {
acc[val] = (acc[val] || []).concat(arr[i]);
return acc;
}, {});
const mas = (data) => data < 2 ? '小与2' : '大于2'
console.log(groupBy([1.2, 2.2, 1.6], mas))
// {
// "大于2": [2.2],
// "小于2": [1.2, 1.6]
// }
通过reduce我们可以把数组分组,感觉使用了这个方法就不太需要引入loadsh了。大家会发现我们在写法上有了一些变化,使用了箭头函数是不是感觉更牛逼了啊,哈哈~
上面这个方法先使用map函数把原数组统一按照我们给定的规则处理一下,在使用reduce遍历的时候,我们就可以根据map返回相同的结果进行分组,再通过第三个参数拿到索引,赋进去值
9、对象的过滤
let arr = [{
id: 1,
name: 'aa'
}, {
id: 2,
name: 'bb'
}]
const filobj = (arr, key) => arr.reduce((prev, next) => prev.concat(next[key]), [])
console.log(filobj(arr, 'name')) //["aa", "bb"]
太简单不要了,是哇,从一个数组对象中找到我们想要的值
10、Promise按照顺序执行
const runPromisesInSeries = ps => ps.reduce((p, next) => p.then(next), Promise.resolve());
const delay = d => new Promise(r => setTimeout(r, d));
const print = args => new Promise(r => r(args));
runPromisesInSeries([() => delay(1000), () => delay(2000), () => print('hello')])
3秒后最后返回的是hello