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] 求数组的和
let arr = [1,2,3,4]; arr.reduce(function(pre,cur){return pre + cur}); // return 10
实际上reduce还有很多重要的用法,这是因为累加器的值可以不必为简单类型(如数字或字符串),它也可以是结构化类型(如数组或对象),这使得我们可以用它做一些其他有用的事情,比如:
- 将数组转换为对象
- 展开更大的数组
- 在一次遍历中进行两次计算
- 将映射和过滤函数组合
- 按顺序运行异步函数
将数组转化为对象
在实际业务开发中,你可能遇到过这样的情况,后台接口返回的数组类型,你需要将它转化为一个根据id值作为key,将数组每项作为value的对象进行查找。
例如:
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 = peoplearr.reduce(keybyusernamereducer, {}); console.log(userobj);
将小数组展开成大数组
试想这样一个场景,我们将一堆纯文本行读入数组中,我们想用逗号分隔每一行,生成一个更大的数组名单。
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.username}` : b.username } const userwithemail = userlist.filter(notemptyemail); 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: [ … ]}
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
上一篇: PyQt5--ButtonDrag
下一篇: 实现基于事件通知的.Net套接字