0%

万能的reduce高阶函数

最近学习react 高阶组件整得我有点难受,一度怀疑自己的智商,虽然知道高阶组件和高阶函数其实是一类东西,但是就是带入不了,所以还是静下心来,好好撸一下高阶函数,凡事还是不能操之过急,太着急的结果往往不如人意。

开始reduce之前,附上今天看到的一段代码

1
2
3
4
5
6
7
8
9
10
11
let arr = [1,2,3];
arr.reduce((pre,cur)=>{
pre.then(()=>{
return new Promise(resolve=>{
setTimeout(() => {
resolve(console.log(cur))
}, 1000);
})
})
return pre;
},Promise.resolve())

猜猜这段代码实现了啥,刚看到的时候给我给整蒙了,不好意思,奈何道行太浅,我看不懂~~~~

所以决定突破高阶组件就从这个reduce函数开始吧,以前真不怎么去用reduce,一直理解成累加器,今天才知道大错特错,准确点来说,应该是一个累计器,自我感觉这词也不是很准确。

reduce介绍

首先reduce是一个高阶函数,它的参数可以是两个参数,第一个参数是函数,第二个参数是初始值,这个初始值很重要,待会再说。先说下第一个参数函数,这个参数函数呢它自己也带有四个参数(pre,value,index, arr),reduce会对arr中的每一个值调用这个参数函数。现在说下reduce的第二个参数,它是函数参数pre的初始值,如果参数函数有返回值,就可以利用reduce的第二个参数去处理这个返回值,具体怎么处理后面细细道来。

现在先来看一段代码

1
2
3
4
5
let arr = [1,2,3];
arr.reduce((pre,current)=>{
console.log(cur);
},9)
//输出:1,2,3

可以看到这段代码reduce的参数函数里面并没有返回值,所以不管reduce的第二个参数是什么,它只是对cur值每个current值进行了console.log处理。

这个效果是不是和forEach的功能一摸一样,我们现在用它来实现forEach功能

实现forEach

1
2
3
4
5
6
7
8
9
10
11
12
const arr = [1, 2, 3];
function forEach(arr,callback){
arr.reduce((pre, current, index,array)=>{
//直接执行callback,这个回调函数在forEach执行的时候直接作为参数写进去
//这也能解释为什么forEach没有返回值
callback(current, index,array)
},0)
}

forEach(arr, (value, index, array)=>{
console.log(value);
})

实现map

1
2
3
4
5
6
7
8
9
10
11
12
13
const arr = [1, 2, 3];

function map(arr,callback){
return arr.reduce((pre, current, index,array)=>{
let result= callback(current, index,array)
pre.push(result)
return pre;
},[])
}
let newArr =map(arr, (value, index, array)=>{
return value*10;
})
console.log(newArr);//[10,20,30]

这里可以看出map和forEach的区别,map具有返回值,而foreEach没有返回值,所以forEach更适合对数据的一次性处理,而map则适合于需要对返回值做二次处理的场景。(其实我觉得如果抛开返回值只是对数组数据处理一下,这两个没啥区别,直接用,纠结个啥)

实现filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const arr = [1, 2, 3];

function filter(arr,callback){
//这里不要忘记下面要将整个结果返回回去,因为filter是一个函数,要有返回值,
//下面返回的pre是reduce函数处理的结果
return arr.reduce((pre, current, index,array)=>{
//这里result就是每次callback执行后的返回值,为true或false
let result= callback(current, index,array)
if(result){
pre.push(current)
}
return pre;
},[])

}
let newArr =filter(arr, (value, index, array)=>{
return value>1;
})
console.log(newArr);

上面的代码中我们给pre的初始值是一个空数组,经过判断回调函数的返回值,将arr中大于1的值放到pre中。

实现some

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const arr = [1, 2, 3];
function some(arr,callback){
return arr.reduce((pre, current, index,array)=>{
let result= callback(current, index,array)
if(result){
pre=result;
}
return pre;
},false)

}
let newArr =some(arr, (value, index, array)=>{
return value>2;
})
console.log(newArr);

some函数是数组里面有一个值符合回调函数的条件就返回true,将pre的初始值设置为false可以保证当数组里面所有的值都不符合条件的时候返回false.

实现every

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const arr = [1, 2, 3];
function every(arr,callback){
let flag=true;
return arr.reduce((pre, current, index,array)=>{
let result= callback(current, index,array)
if(!result){
flag=false;
}
return flag;
},)

}
let newArr =every(arr, (value, index, array)=>{
return value>0;
})
console.log(newArr);

every的实现其实不用pre的值,但需要一个flag来标记。遍历数组里面的值,如果有一个不符合直接返回false。

reduce常见的应用场景

求和

1
2
3
4
5
6
7
8
const subjectSummary = [
{id: 1, subject: '语文', score: 86},
{id: 2, subject: '数学', score: 93},
{id: 2, subject: '英语', score: 95},
];
const scoreSum = subjectSummary.reduce((pre, current) => {
return pre + current.score;
}, 0);

数组去重

1
2
3
4
5
6
7
const arr = [1, 2, 3, 2, 3, 1, 8, 5];
const newArr = arr.reduce((pre, current) => {
if (!pre.includes(current)) {
pre.push(current);
}
return pre;
}, []);

统计字符串出现次数

1
2
3
4
5
6
7
8
9
const str = 'aabdddeffghhhhyyy';
const countMapper = str1.split('').reduce((pre, current) => {
if (pre.hasOwnProperty(current)) {
pre[current] += 1;
} else {
pre[current] = 1;
}
return pre;
}, {});

分组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const menuGroup = [
{id: 1, name: '用户中心',type: 1},
{id: 2, name: '设置', type: 2},
{id: 3, name: '消息中心', type: 2},
{id: 4, name: '我的', type: 1},
];

const menuMap = menuGroup.reduce((pre, current) => {
if (current.type === 1) {
pre.menuList.push(current);
}
if (current.type === 2) {
pre.subMenuList.push(current);
}
return pre;
}, {menuList: [], subMenuList: []});

综合这几个例子看下来,我感觉pre更像是一个处理器容器。pre的初始值和pre调用的函数决定了pre将如何处理参数函数中的值。

最后,我们来分析一下刚开始那段让人头疼代码

1
2
3
4
5
6
7
8
9
10
11
let arr = [1,2,3];
arr.reduce((pre,cur)=>{
pre.then(()=>{
return new Promise(resolve=>{
setTimeout(() => {
resolve(console.log(cur))
}, 1000);
})
})
return pre;
},Promise.resolve())

这段代码实现的是将1、2、3每隔一秒输出。

pre的初始值是一个Promise.resolve(),同过调用then来处理pre的resolve状态,而这个then(参数也是一个函数)它将cur的值打印出来并且返回一个promise,且状态为resolve(),那么在下一次执行参数函数的时候,这个新返回的promise又会再一次调用then………. ,有点一直调用then的意味,then就是对返回的新Promise做处理。