数组reduce方法的深度应用
1.reduce方法介绍
reduce()
方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值
-
一个 “reducer” 函数,包含四个参数:
previousValue
:上一次调用callbackFn
时的返回值。在第一次调用时,若指定了初始值initialValue
,其值则为initialValue
,否则为数组索引为 0 的元素array[0]
。currentValue
:数组中正在处理的元素。在第一次调用时,若指定了初始值initialValue
,其值则为数组索引为 0 的元素array[0]
,否则为array[1]
。currentIndex
:数组中正在处理的元素的索引。若指定了初始值initialValue
,则起始索引号为 0,否则从索引 1 起始。array
:用于遍历的数组。
-
initialValue
可选作为第一次调用
callback
函数时参数 previousValue 的值。若指定了初始值initialValue
,则currentValue
则将使用数组第一个元素;否则previousValue
将使用数组第一个元素,而currentValue
将使用数组第二个元素。
返回值
使用 “reducer” 回调函数遍历整个数组后的结果
2.使用reduce方法的好处
reduce方法内部使用了迭代器,可以解决一些递归类的问题,日常开发中可以使用它解决非常复杂的嵌套对象,例如:在处理大量嵌套数据(如 JSON 文件)时,javascript 中很少有基本的数组方法会被重复使用,可以单独使用 reduce 以用更少的代码实现相同的结果。
3.reduce方法的实际应用
3.1.记录数组中元素的次数
场景描述:
需要根据数组当中存在的值,去记录每次值出现的次数 arr=[1,2,3,4,5,1,2,3],那么结果就是[{1:2},{2:2},{3;2},{4:1},{5:1}]
解决思路:
判断查找当前元素是不是在数组中已经存在过了,如果不存在,那么它就是第一次出现,记录一下该元素已经存在一次了,如果存在就在之前记录的次数上加一.
function totalNum(arr) {
const newArr = arr.reduce((pre, cur) => {
if (!pre[cur]) {
pre[cur] = 1;
} else {
pre[cur]++;
}
return pre;
}, {});
return newArr;
}
3.2.解决对象数组的去重问题
场景描述:
一般数组去重可以用对象唯一键值的方式,根据object和set的属性名不能重复来判断,但是对象数组就没法判断
解决思路
使用空对象进行存储,从数组中取出对象元素跟目标对象元素进行比对,如果没有,就将当前对象元素push进当前的数组中,否则目标对象中有,则返回空
function unbalance(arr) {
let obj = Object.create(null);
let newArr = arr.reduce((pre, cur) => {
obj[cur.name] ? "" : (obj[cur.name] = 1 && pre.push(cur));
return pre;
}, []);
return newArr;
}
3.3.计算年龄
场景描述
有一个年龄数组,它代表人们出生的年份,我们只想保留18岁以上的人,并计算出人们的年龄
const years = [1997, 1999, 2001,2012,2017,2020];
const currentYear = (new Date).getFullYear();
const reducer = (accumulator, year) => {
const age = currentYear - year;
if (age < 18) {
return accumulator;
}
accumulator.push({
year,
age
});
return accumulator;
}
const over18Ages = years.reduce(reducer, []);
3.4.树的数据处理
需求分析
在封装树组件的时候,经过后端返回数据,我们需要将数据加工成我们所需要的数据,将扁平化的数据做嵌套处理,如果不使用递归,就可以通过reduce实现
需要处理的数据
{"code":0,"parent":[{"name":"文件夹1","pid":0,"id":1},{"name":"文件夹2","pid":0,"id":2},
{"name":"文件夹3","pid":0,"id":3},{"name":"文件夹1-1","pid":1,"id":4},{"name":"文件夹2-
1","pid":2,"id":5}],"child":[{"name":"文件1","pid":1,"id":1001},{"name":"文件
2","pid":1,"id":1002},{"name":"文件2-1","pid":2,"id":1003},{"name":"文件2-
2","pid":2,"id":1004},{"name":"文件1-1","pid":4,"id":1005},{"name":"文件2-1-1","pid":5,"id":1006}]}
处理后的数据
[{"name":"文件夹1","pid":0,"id":1,"children":[{"name":"文件夹1-1","pid":1,"id":4,"children":
[{"name":"文件1-1","pid":4,"id":1005}]},{"name":"文件1","pid":1,"id":1001},{"name":"文件
2","pid":1,"id":1002}]},{"name":"文件夹2","pid":0,"id":2,"children":[{"name":"文件夹2-
1","pid":2,"id":5,"children":[{"name":"文件2-1-1","pid":5,"id":1006}]},{"name":"文件2-
1","pid":2,"id":1003},{"name":"文件2-2","pid":2,"id":1004}]},{"name":"文件夹3","pid":0,"id":3}]
//1.先扁平化数组
const allData = [...data.parent, ...data.child];
//2.做树的映射表,改成以下结构
//{1:{name:"文件夹1",pid:0,id:1}}
const treeData = allData.reduce((pre, cur) => {
pre[cur["id"]] = cur;
return pre;
}, {});
//3.做数据处理
const newData = allData.reduce((pre, cur) => {
//从当前项中拿到pid
let { pid } = cur;
//根据映射表判断是否有对应的对象
let parent = treeData[pid];
if (parent) {
// debugger
// console.log( parent.children,' parent.children');
parent.children
? parent.children.push(cur)
: (parent.children = [cur]);
} else if (pid ===0) {
pre.push(cur);
}
return pre;
}, []);
3.5 使用函数实现管道
需求分析
有一堆函数,需要将上一函数的返回值作为下个函数的入参进行计算,实现一个管道函数
const double = x => x + x;
const triple = x => 3 * x;
const quadruple = x => 4 * x;
const pipe = (...functions) =>initialValue =>functions.reduce((acc, fn) => fn(acc), initialValue);
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);
multiply6(6); // 36
multiply9(9); // 81
multiply16(16); // 256
multiply24(10); // 240
3.6功能函数集成
需求分析
比如我需要将一个字符串切割成数组,在计算数组的长度,然后根据数组的长度做一系列计算操作
const toWords = (string) => string.split(" ");
const count = (array) => array.length;
const wpm = (wordCount) => wordCount * 30;
const speed = (string, func) =>func.reduce((composed, fn) => fn(composed), string);
speed("wo shi wangsu", [toWords, count, wpm]);
3.7 多个请求的先后执行问题
需求分析
多个请求并发操作,b请求需要在a请求得到相应结果之后操作,除了使用async和await外还可以采用erduce+promise的方式
function runPromiseInSequence(arr, input) {
return arr.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(input)
);
}
function p1(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(a * 5);
}, 1000);
});
}
function p2(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(a * 2);
}, 1000);
});
}
function f3(a) {
return a * 3;
}
function p4(a) {
return new Promise((resolve, reject) => {
resolve(a * 4);
});
}
const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10).then(res=>{
console.log(res);
});
3.8使用reduce迭代器解决递归问题
需求分析
递归函数经常被使用,因为它们的语义定义,,如果可以将递归函数转换为迭代方法,就可以使用
reduce
,reduce
如果做得好,在启用声明性定义的同时,就不会有可能填满函数堆栈的问题。
const factorial = (number) =>
number === 0 ? 1 : number * factorial(number - 1);
const factorial = (number) =>
Array(number)
.fill(number)
.reduce((acc, elem, i) => acc * (elem - i));
转载自:https://juejin.cn/post/7136022142184849438