Javascript数组方法中,相比map
、filter
、forEach
等经常使用的迭代方法,reduce
经常被咱们所忽略,今天一块儿来探究一下reduce
在咱们实战开发当中,能有哪些妙用之处,下面从reduce
语法开始介绍。前端
array.reduce(function(accumulator, arrayElement, currentIndex, arr), initialValue) 复制代码
若传入初始值,accumulator首次迭代就是初始值,不然就是数组的第一个元素;后续迭代中将是上一次迭代函数返回的结果。因此,假如数组的长度为n,若是传入初始值,迭代次数为n;不然为n-1。json
好比实现数组 arr = [1,2,3,4] 求数组的和api
let arr = [1,2,3,4];
arr.reduce(function(pre,cur){return pre + cur}); // return 10
复制代码
实际上reduce还有不少重要的用法,这是由于累加器的值能够没必要为简单类型(如数字或字符串),它也能够是结构化类型(如数组或对象),这使得咱们能够用它作一些其余有用的事情,好比:数组
在实际业务开发中,你可能遇到过这样的状况,后台接口返回的数组类型,你须要将它转化为一个根据id值做为key,将数组每项做为value的对象进行查找。promise
例如:bash
const userList = [
{
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
也能实现这样的需求。异步
function keyByUsernameReducer(acc, person) {
return {...acc, [person.id]: person};
}
const userObj = userList.reduce(keyByUsernameReducer, {});
console.log(userObj);
// {
// 1: {
// id: 1,
// username: 'john',
// sex: 1,
// email: 'john@163.com'
// },
// 2: {
// id: 2,
// username: 'jerry',
// sex: 1,
// email: 'jerry@163.com'
// },
// 3: {
// id: 3,
// username: 'nancy',
// sex: 0,
// email: ''
// }
// }
复制代码
试想这样一个场景,咱们将一堆纯文本行读入数组中,咱们想用逗号分隔每一行,生成一个更大的数组名单。async
const fileLines = [
'Inspector Algar,Inspector Bardle,Mr. Barker,Inspector Barton',
'Inspector Baynes,Inspector Bradstreet,Inspector Sam Brown',
'Monsieur Dubugue,Birdy Edwards,Inspector Forbes,Inspector Forrester',
'Inspector Gregory,Inspector Tobias Gregson,Inspector Hill',
'Inspector Stanley Hopkins,Inspector Athelney Jones'
];
function splitLineReducer(acc, line) {
return acc.concat(line.split(/,/g));
}
const investigators = fileLines.reduce(splitLineReducer, []);
console.log(investigators);
// [
// "Inspector Algar",
// "Inspector Bardle",
// "Mr. Barker",
// "Inspector Barton",
// "Inspector Baynes",
// "Inspector Bradstreet",
// "Inspector Sam Brown",
// "Monsieur Dubugue",
// "Birdy Edwards",
// "Inspector Forbes",
// "Inspector Forrester",
// "Inspector Gregory",
// "Inspector Tobias Gregson",
// "Inspector Hill",
// "Inspector Stanley Hopkins",
// "Inspector Athelney Jones"
// ]
复制代码
咱们从长度为5的数组开始,最后获得一个长度为16的数组。函数
另外一种常见增长数组的状况是flatMap,有时候咱们用map方法须要将二级数组展开,这时能够用reduce实现扁平化性能
例如:
Array.prototype.flatMap = function(f) {
const reducer = (acc, item) => acc.concat(f(item));
return this.reduce(reducer, []);
}
const arr = ["今每天气不错", "", "早上好"]
const arr1 = arr.map(s => s.split(""))
// [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]]
const arr2 = arr.flatMap(s => s.split(''));
// ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
复制代码
有时咱们须要对数组进行两次计算。例如,咱们可能想要计算数字列表的最大值和最小值。咱们能够经过两次经过这样作:
const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4];
const maxReading = readings.reduce((x, y) => Math.max(x, y), Number.MIN_VALUE);
const minReading = readings.reduce((x, y) => Math.min(x, y), Number.MAX_VALUE);
console.log({minReading, maxReading});
// {minReading: 0.2, maxReading: 5.5}
复制代码
这须要遍历咱们的数组两次。可是,有时咱们可能不想这样作。由于.reduce()让咱们返回咱们想要的任何类型,咱们没必要返回数字。咱们能够将两个值编码到一个对象中。而后咱们能够在每次迭代时进行两次计算,而且只遍历数组一次:
const readings = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4];
function minMaxReducer(acc, reading) {
return {
minReading: Math.min(acc.minReading, reading),
maxReading: Math.max(acc.maxReading, reading),
};
}
const initMinMax = {
minReading: Number.MAX_VALUE,
maxReading: Number.MIN_VALUE,
};
const minMax = readings.reduce(minMaxReducer, initMinMax);
console.log(minMax);
// {minReading: 0.2, maxReading: 5.5}
复制代码
仍是先前那个用户列表,咱们但愿找到没有电子邮件地址的人的用户名,返回它们用户名用逗号拼接的字符串。一种方法是使用三个单独的操做:
将它们放在一块儿可能看起来像这样:
function notEmptyEmail(x) {
return !!x.email
}
function notEmptyEmailUsername(a, b) {
return a ? `${a},${b}` : b
}
const userWithEmail = userList.filter(notEmptyEmail);
const usernameWithEmail = userWithEmail.map((user=> user.username)
const userWithEmailFormatStr = userWithEmail.reduce(notEmptyEmailUsername, '');
console.log(userWithEmailFormatStr);
// 'john,jerry'
复制代码
如今,这段代码是彻底可读的,对于小的样本数据不会有性能问题,可是若是咱们有一个庞大的数组呢?若是咱们修改咱们的reducer回调,那么咱们能够一次完成全部事情:
function notEmptyEmail(x) {
return !!x.email
}
function notEmptyEmailUsername(usernameAcc, person){
return (notEmptyEmail(person))
? (usernameAcc ? `${usernameAcc},${person.username}` : `${person.username}`) : usernameAcc;
}
const userWithEmailFormatStr = userList.reduce(notEmptyEmailUsername, '');
console.log(userWithEmailFormatStr);
// 'john,jerry'
复制代码
在这个版本中,咱们只遍历一次数组,通常建议使用filter
和map
的组合,除非发现性能问题,才推荐使用reduce
去作优化。
咱们能够作的另外一件事.reduce()是按顺序运行promises(而不是并行)。若是您对API请求有速率限制,或者您须要将每一个prmise的结果传递到下一个promise,reduce
能够帮助到你。
举一个例子,假设咱们想要为userList
数组中的每一个人获取消息。
function fetchMessages(username) {
return fetch(`https://example.com/api/messages/${username}`)
.then(response => response.json());
}
function getUsername(person) {
return person.username;
}
async function chainedFetchMessages(p, username) {
// In this function, p is a promise. We wait for it to finish,
// then run fetchMessages().
const obj = await p;
const data = await fetchMessages(username);
return { ...obj, [username]: data};
}
const msgObj = userList
.map(getUsername)
.reduce(chainedFetchMessages, Promise.resolve({}))
.then(console.log);
// {glestrade: [ … ], mholmes: [ … ], iadler: [ … ]}
复制代码
async
函数返回一个 Promise 对象,能够使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操做完成,再接着执行函数体内后面的语句。
请注意,在此咱们传递Promise做为初始值Promise.resolve()
,咱们的第一个API调用将当即运行。
下面是不使用async
语法糖的版本
function fetchMessages(username) {
return fetch(`https://example.com/api/messages/${username}`)
.then(response => response.json());
}
function getUsername(person) {
return person.username;
}
function chainedFetchMessages(p, username) {
// In this function, p is a promise. We wait for it to finish,
// then run fetchMessages().
return p.then((obj)=>{
return fetchMessages(username).then(data=>{
return {
...obj,
[username]: data
}
})
})
}
const msgObj = peopleArr
.map(getUsername)
.reduce(chainedFetchMessages, Promise.resolve({}))
.then(console.log);
// {glestrade: [ … ], mholmes: [ … ], iadler: [ … ]}
复制代码
PS:更多前端资讯、技术干货,请关注公众号「前端新视界」