JavaScript模式-深入了解数组Array#reduce(译)

这里写图片描述

什么?

在Hashnode,每当我们将代码推到代码库中,我们邀请会其他人,对其进行审查。代码的审查会议,是非常棒的; 主要是因为我们可以利用这个机会从中学习到独特的东西。

每当审阅者遇到可以以更好的方式优化/重构/重写的代码部分时,他会记下它,并将其传递给每个人,以供将来参考。

这个故事是我做的笔记,然后经过思考我想把它变成一个成熟的故事。

希望文章结束的时候,当使用数组在JavaScript,你能欣赏到Array#reduce的一些优雅,以及如何使用它来编写高效的代码。

让我们开始吧!

基础

作为一个程序员,经常会遇到,你必须处理数据数组,并将所述数据转换为所需的格式。

JavaScript有一个可用于数组对象的函数reduce,这有助于我们将数组数据转换为所需的格式。reduce有两个参数:

  • 一个reducer函数,它被应用于一个accumulator,并且每个item在数组中(从左到右),以将其减少为单个值
  • a initialValue作为累加器

这些只是口说!让我们看看一些代码。

下面的例子,对数组中数字求和,是一个典型的数组,每当介绍时Array#reduce就给出。

const numbers = [10, 20, 30];
const reducer = (accumulator, item) => {
    return accumulator + item;
};

const initialValue = 0;
const total = numbers.reduce(reducer, initialValue);

// The following outputs: "The sum is: 60"
console.log("The sum is: ", total);
重要“陷阱”备注

要记得在一个reducer 函数中要有一个返回值。你的返回值会变成accumulator值,item指向数组中的下一个。

在上面的简单reducer函数中看起来很简单,但是在复杂的reducer函数中,忘记returnArray#reduce的主要“bug”原因之一。


在我们对如何利用Array#reduce改写我们的代码库中的一小部分进行讨论之前; 通过让我运行你的几个指针,我发现你是否那么直观的明白他们。

#1:使用reduce可以操作数组来减少它,不一定是原始值,而是对象(包括数组)

让我们编写一个程序来查找数组中6的倍数的总数,并输出一个对象,如下所示:{ totalMultiplesOfSix: 1, totalNonMultiplesOfSix: 1 }对于一个输入:[6, 7]

const numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
const multiplesOfSixInfo = numArray => numArray.reduce(
    (acc, item) => {
        (item % 6 === 0)
            ? acc.totalMultiplesOfSix += 1
            : acc.totalNonMultiplesOfSix += 1;

        // Don't forget to return the accumulator, and make it available ...
        // ...to the next item in numArray
        return acc;
    },
    { totalMultiplesOfSix: 0, totalNonMultiplesOfSix: 0 }
);

// The following outputs "{totalMultiplesOfSix: 3, totalNonMultiplesOfSix: 7}"
console.log(multiplesOfSixInfo(numbers));

请注意,传入的初始值numArray#reduce是对象{ totalMultiplesOfSix: 0, totalNonMultiplesOfSix: 0 }

#2:mapfilter操作,可以认为是reduce操作

让我们直接通过一些代码了解上述指针的含义。这里有一些操作map的代码:

const numbers = [10, 20, 30];
const squaresOfNumbers = numArray => numbers.map(item => item * item;);

// The following outputs: "[100, 400, 900]"
console.log(squaresOfNumbers(numbers));

上面的代码可以用Array#reduce重写为:

const numbers = [10, 20, 30];
const squaresOfNumbers = numArray => numbers.reduce(
    (acc, item) => {
        acc.push(item * item);
        return acc;
    },
    []
);

// The following outputs: "[100, 400, 900]"
console.log(squaresOfNumbers(numbers));

现在,让我们来看一个filter操作:

const numbers = [10, 20, 30];
const multiplesOfSix = numArray => numArray.filter(item => item % 6 === 0);

// The following outputs: "[30]"
console.log(multiplesOfSix(numbers));

上面的代码可以用Array#reduce重写为:

const numbers = [10, 20, 30];
const multiplesOfSix = numArray => numArray.reduce(
    (acc, item) => {
        if (item % 6 === 0) acc.push(item);
        return acc;
    },
    []
);

// The following outputs: "[30]"
console.log(multiplesOfSix(numbers));

现在我们已经讨论了#2,这是理解下一点的前提 - 让我们跳到#3。

#3:您可以使用reduce重写多个操作数组到单个op。

第一眼看到#2下的代码,你可能已经认为代码具有mapfilter比它对应reduce代码更简洁和可读性更高。

但是当你有一个有很多值的数组时,对它做多个操作 - 例如,a map,后面跟着a map,后面跟a filter-可以获得资源密集型

它在像上面这样的地方,其中单个reduce操作是更有益的模式,而不是阵列上的多个操作; 即使后者导致简洁的代码。

一个reduce统治他们所有

a.k.a.#3 - 您可以使用reduce 将数组上的多个操作重写为单个操作。一个有益的模式,特别是在处理大型数组时

让我们看看一些代码,了解更多关于这个模式; 以及“ 有益模式 ”和“* 资源密集型* ”在这里是什么。

我们手头的问题是为给定的一组节点获得一个(唯一)电子邮件数组,所有跟随者。因此,对于一个示例,dummy nodes数据集如下:

var nodes = [
    { 
        name: 'java',
        followers: [
            { name: 'ABC', email: 'abc@abc.com' },
            { name: 'IJK', email: 'ijk@ijk.com' },
            { name: 'LMN', email: 'lmn@lmn.com' }
        ]
    },
    { 
        name: 'javascript',
        followers: [
            { name: 'ABC', email: 'abc@abc.com' },
            { name: 'IJK', email: 'ijk@ijk.com' },
            { name: 'XYZ', email: 'xyz@xyz.com' }
        ]
    },
    { 
        name: 'programming',
        followers: [
            { name: 'XYZ', email: 'abc@abc.com' },
            { name: 'IJK', email: 'ijk@ijk.com' },
            { name: 'PQR' }
        ]
    }
]

…函数的输出getSetOfFollowerEmails(nodes)预计为:

[
    'abc@abc.com',
    'ijk@ijk.com',
    'lmn@lmn.com',
    'xyz@xyz.com'
]
旧代码

这是我遇到的代码审查。下面的代码非常简洁,甚至我grand-mom后会得到一个去!:D

import _ from 'lodash';

const getSetOfFollowerEmails = (nodes) => {    
    let followers = _.flatten(nodes.map(node => node.followers));
    followers = followers.filter(follower => follower.email ? true : false);
    followers = _.uniqBy(followers, 'email');

    const followerEmails = followers.map(follower => follower.email);
    return followerEmails;
}
改进代码

但是…我们改变了它的实现。虽然下面的代码不像上面的代码那样简洁或可读; 它有它的加分点。阅读代码,我们会看到为什么!

const getSetOfFollowerEmails = (nodes) => {
    return _.uniq(nodes.reduce(
        (followerEmails, node) => {
            node.followers.forEach(
                follower => {
                    if (follower.email) {
                        followerEmails.push(follower.email);
                    }
                }
            );

            // Don't forget to return the accumulator;
            return followerEmails;
        },
        // Initial accumulator here, is an empty array
        []
    ));
}

更新: Robert Stires优化了上面的代码段,甚至更多的是通过替换forEach操作,用一个reduce操作。

理论

最明显的观察是,作为followers增长的数量,旧代码是无效率的,因为,如果你按你说法,我们通过followers多次迭代数组; 但是在改进的代码中,我们只做了两次。

但是让我们用数字验证上面的观察。让我们创建一个虚拟的大型nodes数据集,让我们使用这两个实现nodes,找出哪个更好。

这样做的必要代码可以框架如下:

const getEmailsUsingSingleReduceOp = (nodes) => { ...
const getEmailsUsingMultipleArrayOps = (nodes) => { ...

const nodes = [];
for (let i = 0; i < 50; i++) {
    const followers = [];
    for (let j = 0; j < 5000; j++) {
        followers.push({ name: `ABC${i}${j}`, email: `abc${i}${j}@abc.com` });
    }
    nodes.push({
        name: `node${i}`,
        followers: followers
    });
}

console.time('Multiple Array Ops.');
getEmailsUsingMultipleArrayOps(nodes);
console.timeEnd('Multiple Array Ops.');

console.time('Single Reduce Op.');
getEmailsUsingSingleReduceOp(nodes);
console.timeEnd('Single Reduce Op.');

上述代码输出(大约值):

Multiple Array Ops.: 535.941ms
Single Reduce Op.: 149.408ms

正如你可以看到,getEmailsUsingSingleReduceOp优于 getEmailsUsingMultipleArrayOps 3.6倍。

结论

我们已经看到了基础知识Array#reduce以及如何利用它来改进对数组进行多个操作的代码; 从而产生更好的性能代码。

还有其他很酷的模式,你可以通过使用实现Array#reduce。例如,你可以创建函数流水线(你也可以创建你可以突破,中途使用Array#some)的函数流水线。我期待着在不同的故事中覆盖这些。

结束!

(译自)https://hashnode.com/post/javascript-patterns-wrangling-arrays-like-a-boss-with-arrayreduce-cj0a7avja017jx7534y7338pr

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值