Javascript数组方法reduce的妙用之处分享
前言
Javascript数组方法中,相比map、filter、forEach等常用的迭代方法,reduce常常被我们所忽略,今天一起来探究一下reduce在我们实战开发当中,能有哪些妙用之处,下面从reduce语法开始介绍。
语法
array.reduce(function(accumulator,arrayElement,currentIndex,arr),initialValue)
若传入初始值,accumulator首次迭代就是初始值,否则就是数组的第一个元素;后续迭代中将是上一次迭代函数返回的结果。所以,假如数组的长度为n,如果传入初始值,迭代次数为n;否则为n-1。
比如实现数组arr=[1,2,3,4]求数组的和
letarr=[1,2,3,4];
arr.reduce(function(pre,cur){returnpre+cur});//return10
实际上reduce还有很多重要的用法,这是因为累加器的值可以不必为简单类型(如数字或字符串),它也可以是结构化类型(如数组或对象),这使得我们可以用它做一些其他有用的事情,比如:
- 将数组转换为对象
- 展开更大的数组
- 在一次遍历中进行两次计算
- 将映射和过滤函数组合
- 按顺序运行异步函数
将数组转化为对象
在实际业务开发中,你可能遇到过这样的情况,后台接口返回的数组类型,你需要将它转化为一个根据id值作为key,将数组每项作为value的对象进行查找。
例如:
constuserList=[
{
id:1,
username:'john',
sex:1,
email:'john@163.com'
},
{
id:2,
username:'jerry',
sex:1,
email:'jerry@163.com'
},
{
id:3,
username:'nancy',
sex:0,
email:''
}
];
如果你用过lodash这个库,使用_.keyBy这个方法就能进行转换,但用reduce也能实现这样的需求。
functionkeyByUsernameReducer(acc,person){
return{...acc,[person.id]:person};
}
constuserObj=peopleArr.reduce(keyByUsernameReducer,{});
console.log(userObj);
将小数组展开成大数组
试想这样一个场景,我们将一堆纯文本行读入数组中,我们想用逗号分隔每一行,生成一个更大的数组名单。
constfileLines=[
'InspectorAlgar,InspectorBardle,Mr.Barker,InspectorBarton',
'InspectorBaynes,InspectorBradstreet,InspectorSamBrown',
'MonsieurDubugue,BirdyEdwards,InspectorForbes,InspectorForrester',
'InspectorGregory,InspectorTobiasGregson,InspectorHill',
'InspectorStanleyHopkins,InspectorAthelneyJones'
];
functionsplitLineReducer(acc,line){
returnacc.concat(line.split(/,/g));
}
constinvestigators=fileLines.reduce(splitLineReducer,[]);
console.log(investigators);
//[
//"InspectorAlgar",
//"InspectorBardle",
//"Mr.Barker",
//"InspectorBarton",
//"InspectorBaynes",
//"InspectorBradstreet",
//"InspectorSamBrown",
//"MonsieurDubugue",
//"BirdyEdwards",
//"InspectorForbes",
//"InspectorForrester",
//"InspectorGregory",
//"InspectorTobiasGregson",
//"InspectorHill",
//"InspectorStanleyHopkins",
//"InspectorAthelneyJones"
//]
我们从长度为5的数组开始,最后得到一个长度为16的数组。
另一种常见增加数组的情况是flatMap,有时候我们用map方法需要将二级数组展开,这时可以用reduce实现扁平化
例如:
Array.prototype.flatMap=function(f){
constreducer=(acc,item)=>acc.concat(f(item));
returnthis.reduce(reducer,[]);
}
constarr=["今天天气不错","","早上好"]
constarr1=arr.map(s=>s.split(""))
//[["今","天","天","气","不","错"],[""],["早","上","好"]]
constarr2=arr.flatMap(s=>s.split(''));
//["今","天","天","气","不","错","","早","上","好"]
在一次遍历中进行两次计算
有时我们需要对数组进行两次计算。例如,我们可能想要计算数字列表的最大值和最小值。我们可以通过两次通过这样做:
constreadings=[0.3,1.2,3.4,0.2,3.2,5.5,0.4];
constmaxReading=readings.reduce((x,y)=>Math.max(x,y),Number.MIN_VALUE);
constminReading=readings.reduce((x,y)=>Math.min(x,y),Number.MAX_VALUE);
console.log({minReading,maxReading});
//{minReading:0.2,maxReading:5.5}
这需要遍历我们的数组两次。但是,有时我们可能不想这样做。因为.reduce()让我们返回我们想要的任何类型,我们不必返回数字。我们可以将两个值编码到一个对象中。然后我们可以在每次迭代时进行两次计算,并且只遍历数组一次:
constreadings=[0.3,1.2,3.4,0.2,3.2,5.5,0.4];
functionminMaxReducer(acc,reading){
return{
minReading:Math.min(acc.minReading,reading),
maxReading:Math.max(acc.maxReading,reading),
};
}
constinitMinMax={
minReading:Number.MAX_VALUE,
maxReading:Number.MIN_VALUE,
};
constminMax=readings.reduce(minMaxReducer,initMinMax);
console.log(minMax);
//{minReading:0.2,maxReading:5.5}
将映射和过滤合并为一个过程
还是先前那个用户列表,我们希望找到没有电子邮件地址的人的用户名,返回它们用户名用逗号拼接的字符串。一种方法是使用两个单独的操作:
- 获取过滤无电子邮件后的条目
- 获取用户名并拼接
将它们放在一起可能看起来像这样:
functionnotEmptyEmail(x){
return!!x.email
}
functionnotEmptyEmailUsername(a,b){
returna?`${a},${b.username}`:b.username
}
constuserWithEmail=userList.filter(notEmptyEmail);
constuserWithEmailFormatStr=userWithEmail.reduce(notEmptyEmailUsername,'');
console.log(userWithEmailFormatStr);
//'john,jerry'
现在,这段代码是完全可读的,对于小的样本数据不会有性能问题,但是如果我们有一个庞大的数组呢?如果我们修改我们的reducer回调,那么我们可以一次完成所有事情:
functionnotEmptyEmail(x){
return!!x.email
}
functionnotEmptyEmailUsername(usernameAcc,person){
return(notEmptyEmail(person))
?(usernameAcc?`${usernameAcc},${person.username}`:`${person.username}`):usernameAcc;
}
constuserWithEmailFormatStr=userList.reduce(notEmptyEmailUsername,'');
console.log(userWithEmailFormatStr);
//'john,jerry'
在这个版本中,我们只遍历一次数组,一般建议使用filter和map的组合,除非发现性能问题,才推荐使用reduce去做优化。
按顺序运行异步函数
我们可以做的另一件事.reduce()是按顺序运行promises(而不是并行)。如果您对API请求有速率限制,或者您需要将每个prmise的结果传递到下一个promise,reduce可以帮助到你。
举一个例子,假设我们想要为userList数组中的每个人获取消息。
functionfetchMessages(username){
returnfetch(`https://example.com/api/messages/${username}`)
.then(response=>response.json());
}
functiongetUsername(person){
returnperson.username;
}
asyncfunctionchainedFetchMessages(p,username){
//Inthisfunction,pisapromise.Wewaitforittofinish,
//thenrunfetchMessages().
constobj=awaitp;
constdata=awaitfetchMessages(username);
return{...obj,[username]:data};
}
constmsgObj=userList
.map(getUsername)
.reduce(chainedFetchMessages,Promise.resolve({}))
.then(console.log);
//{glestrade:[…],mholmes:[…],iadler:[…]}
async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
请注意,在此我们传递Promise作为初始值Promise.resolve(),我们的第一个API调用将立即运行。
下面是不使用async语法糖的版本
functionfetchMessages(username){
returnfetch(`https://example.com/api/messages/${username}`)
.then(response=>response.json());
}
functiongetUsername(person){
returnperson.username;
}
functionchainedFetchMessages(p,username){
//Inthisfunction,pisapromise.Wewaitforittofinish,
//thenrunfetchMessages().
returnp.then((obj)=>{
returnfetchMessages(username).then(data=>{
return{
...obj,
[username]:data
}
})
})
}
constmsgObj=peopleArr
.map(getUsername)
.reduce(chainedFetchMessages,Promise.resolve({}))
.then(console.log);
//{glestrade:[…],mholmes:[…],iadler:[…]}
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。