您好,欢迎来到飒榕旅游知识分享网。
搜索
您的当前位置:首页Javascript数组方法reduce的妙用之处分享

Javascript数组方法reduce的妙用之处分享

来源:飒榕旅游知识分享网

若传入初始值,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: [ … ]}

    总结

    Copyright © 2019- sarr.cn 版权所有

    违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

    本站由北京市万商天勤律师事务所王兴未律师提供法律服务