资讯专栏INFORMATION COLUMN

Javascript中数组方法reduce的妙用之处

yiliang / 1862人阅读

摘要:数组方法中,相比等常用的迭代方法,常常被我们所忽略,今天一起来探究一下在我们实战开发当中,能有哪些妙用之处,下面从语法开始介绍。按顺序运行异步函数我们可以做的另一件事是按顺序运行而不是并行。函数返回一个对象,可以使用方法添加回调函数。

Javascript数组方法中,相比mapfilterforEach等常用的迭代方法,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"

在这个版本中,我们只遍历一次数组,一般建议使用filtermap的组合,除非发现性能问题,才推荐使用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:更多前端资讯、技术干货,请关注公众号「前端新视界

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104519.html

相关文章

  • reduce妙用

    摘要:为数组的每一项为数组的下标为原数组如果没传第一次为数组的第一项,之后为的值。为从数组的第二项开始的每一项不受影响下划线转驼峰数组扁平化二维转一维数组转对象路由数组转对象 reduce 数组的方法,有两个参数 回调函数callback 和 initialValue回调有四个参数 prev、next、index、arrinitialValue:可选参数,作为callback第一次的prev...

    blastz 评论0 收藏0
  • 简约强大数组操作组合

    摘要:一个归并方法第一个参数是每一项上调用的函数,该函数有四个参数累加回调返回值他是上一次调用时返回的累积值,或数组中正在处理的元素数组中正在处理的当前元素的索引。统计数组中重复出现项的个数,用对象表示。 前言 在实际js开发中对数组操作频率非常高,看过一些小伙伴的一些用法,挺有意思,在这里小记(不全)一下,备忘。 showImg(https://segmentfault.com/img/r...

    chenjiang3 评论0 收藏0
  • 全本 | iKcamp翻译 | 《JavaScript 轻量级函数式编程》|《你不知道JS》姊妹篇

    摘要:本书主要探索函数式编程的核心思想。我们在中应用的仅仅是一套基本的函数式编程概念的子集。我称之为轻量级函数式编程。通常来说,关于函数式编程的书籍都热衷于拓展阅读者的知识面,并企图覆盖更多的知识点。,本书统称为函数式编程者。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson - 《You-Dont-Know-JS》作者 译者团队(排名不分先后)...

    paney129 评论0 收藏0
  • 数组reduce方法高级技巧

    摘要:我们在的第二个参数里面初始了回调函数第一个参数的类型和值,将字符串转化为数组,那么迭代的结果将是一个对象,对象的每一项值就是字符串的字母。 因为用for循环被老大鄙视之后,这几天都在偷偷摸摸的研究数组的那几个迭代方法。使用下来,感觉确实妙用无穷,仿佛自己的逼格在无形中变得高大了一点点,哈哈,上一篇文章的简单介绍确实有点糙,因此决定重新一些总结文章。 这篇文章就是专门总结reduce方法...

    yvonne 评论0 收藏0
  • 数组reduce方法高级技巧

    摘要:我们在的第二个参数里面初始了回调函数第一个参数的类型和值,将字符串转化为数组,那么迭代的结果将是一个对象,对象的每一项值就是字符串的字母。 因为用for循环被老大鄙视之后,这几天都在偷偷摸摸的研究数组的那几个迭代方法。使用下来,感觉确实妙用无穷,仿佛自己的逼格在无形中变得高大了一点点,哈哈,上一篇文章的简单介绍确实有点糙,因此决定重新一些总结文章。 这篇文章就是专门总结reduce方法...

    mudiyouyou 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<