什么?
在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函数中,忘记return
是Array#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:map
和filter
操作,可以认为是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下的代码,你可能已经认为代码具有map
和filter
比它对应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
)的函数流水线。我期待着在不同的故事中覆盖这些。
结束!