原文:https://medium.freecodecamp.org/reduce-f47a7da511a9
JavaScript中的Reduce方法是函数式编程的基础。让我们来探讨它是如何工作的,在什么时候使用它,又可以用它做什么有趣的事情。
基本的Reduction
何时使用
你有一个都为数字的数组,并且想把它们都加起来。
1 | const euros = [29.76, 41.85, 46.5]; |
如何使用
- 在上面的例子中,Reduce接收两个变量,总数和当前数字。
- reduce方法遍历数组中的每个数字就像进行一个for循环。
- 当循环开始时,total变量是数组最左侧的元素(29.76),而当前数字是它的下一个元素(41.85)。
- 在这个典型的例子中,我们想把当前数字加到总数上
- 计算会在数组的每一个数字间重复,每次当前变量都会变成数组中的下一个元素,指针像右移动。
- 当数组中没有数字剩下时,方法会停止并返回total。
ES5版本的Reduce方法
如果你之前完全未使用过ES6的语法,不要让上面那个例子吓到你,它其实等同于这样的写法:
1
2
3
4
5var euros = [29.76, 41.85, 46.5];
var sum = euros.reduce( function(total, amount){
return total + amount
});
sum // 118.11
我们使用const
代替var
,我们将function
用箭头函数代替,并且省略renturn
。
我将使用ES6的语法编写接下来的例子,因为它更简洁并且留下错误的空间会更小。
使用Reduce寻找平均数
除了记录总数,你可以在返回最终值之前将总和除以数组长度。
要做到这一点需要用到Reduce方法中的其它参数。这些参数中的第一个是index
,就像for循环。index指的是在数组中reduce已经循环的次数。最后一个变量是数组本身。
1 | const euros = [29.76, 41.85, 46.5]; |
用Reductions实现map和filter效果
如何你可以使用reduce方法求出平均数,那么你可以以任何你想要的方式使用它。
举个例子,如果你想在求和之前让数字翻倍或者将每个数字减半,或者仅仅相加大于10的数字。我想说的是JavaScript中的reduce方法可以给你一个迷你的CodePen,你可以用它实现任何你想实现的逻辑。它将会在数组的每个元素间重复逻辑,最后返回单个值。
如何你不想返回单个值,你还可以通过Reduce方法将一个数组变成一个新的数组。
例如,可以通过reduce创建一个新的数组,将原数组中每个元素翻倍。为此,我们需要将累加器的初始值设置为空数组。
初始值是reduction开始时的total参数,设置初始值可以在圆括号内,花括号后的位置增加一个逗号,后面跟随你需要设置的初始值。(下面代码中加粗的部分。译者注:排版问题翻译后代码不好加粗,就是最后一行0的位置)
1 | const average = euros.reduce((total, amount, index, array) => { |
在前面的例子中,由于初始值是0所以我省略了它,由于省略初始值,累加器的初始值默认为数组中的第一个数字。
通过设置初始值为一个空数组,我们可以依次将每个元素push进去,如果我们想将一个数组中的数字翻倍加入另一个数组,我们需要将数组中的每个元素乘以2再push,当没有更多元素可以push的时候返回最终结果。
1 | const euros = [29.76, 41.85, 46.5]; |
我们已经创建了一个新数组并且将原先数组中每个元素加倍,我们也可以通过在reducer中添加if语句来过滤掉我们不想加倍的数字。
1 | const euro = [29.76, 41.85, 46.5]; |
这些方式可以让我们用reduce方法重写map和filter方法。
对于这些例子而言,可能使用map或filter方法会更便于使用。但是当你有大量数据需要同时使用map和filter处理,使用reduce的好处就显示出来了。
如果你将map和filter连在一起链式调用就需要遍历两次,首先遍历每个值进行过滤,然后将过滤出的值进行map操作,但是使用reduce你可以通过一次操作过滤然后map。
当你开始链式调用多个如map、filter之类的方法时,你应该需要注意此时用reduce处理数据会更快。
使用Reduce创建一个计数集合
何时使用
你想知道一个集合中的每一项中的个数1
2
3
4
5
6const fruitBasket = ['banana', 'cherry', 'orange', 'apple', 'cherry', 'orange', 'apple', 'banana', 'cherry', 'orange', 'fig' ];
const count = fruitBasket.reduce( (tally, fruit) => {
tally[fruit] = (tally[fruit] || 0) + 1 ;
return tally;
} , {})
count // { banana: 2, cherry: 3, orange: 3, apple: 2, fig: 1 }
需要对一个数组中的元素进行计数,初始值一定为一个空对象而不是一个空数组。
因为我们需要返回一个键值对的对象。1
2
3
4fruitBasket.reduce( (tally, fruit) => {
tally[fruit] = 1;
return tally;
}, {})
在我们的第一次操作中,我们将key值以当前值命名并给之初始值1。
这让我们拥有了一个以所有水果名称为key的对象,每个key的value为1。我们想让水果每次重复时数量增长。
为此,第二次循环我们开始检查当前集合中是否已经包含了该key值了,如果没有就创建,如果有就将值加1.
1 | fruitBasket.reduce((tally, fruit) => { |
我以更清晰的方式重写了刚才的逻辑。
使用reduce减少数组嵌套
我们可以使用reduce来将嵌套的数据平铺到一个数组中。
我们将初始值设置成一个空数组,然后将当前值连接到需要返回的数组上。
1 | const data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; |
信息往往以更复杂的方式嵌套。例如,假设我们只想要下面的数据变量中的所有颜色。
1 | const data = [ |
我们遍历每个对象获取颜色,然后遍历每个amount.c,将颜色推入数组,并返回total。
1 | const colors = data.reduce((total, amount) => { |
如果我们仅仅需要不重复的颜色,我们可以在元素push入数组之前进行检查元素是否存在。
1 | const uniqueColors = data.reduce((total, amount) => { |
Reduce 管道
reduce方法有趣的一点是你可以像数字和字符串一样对函数进行reduce操作。
假设我们有一组简单的数学函数。这些功能允许我们增加,减少,加倍和减半。
1 | function increment(input) { return input + 1;} |
无论出于何种原因,我们需要增加,然后加倍,然后减少一个数额。
你可以编写一个接受输入的函数,然后返回(input + 1)* 2 - 1。问题是我们知道我们需要增加三次,然后加倍,然后减少它,然后在未来某个时候将其减半,但是我们不希望每次都重写我们的函数,所以我们使用reduce来创建管道。
1 | let pipeline = [increment, double, decrement]; |
我们不是对一系列的值进行reduce处理,而是将函数进行reduce处理形成管道。为了让它运行,我们将初始值设置为我们想要转换的数字。
1 | const result = pipeline.reduce(function(total, func) { |
因为管道是一个数组,所以是非常容易修改的,如果我们想减少三次,然后加倍,减少它,然后减半,我们只改变管道。
1 | var pipeline = [ |
reduce方法可以完全保持一致。
避免愚蠢的错误
如果你没有传入初始值,reduce将会假设数组中的第一项是您的初始值。这在前面的几个例子中运行良好,因为我们是将数字列表加起来。
但是如果你想要收集水果,而且你忽略了初始值,那么事情会变得很奇怪。不输入初始值是一个容易犯的错误,也是调试时应该检查的第一件事情之一。
另一个常见错误是忘记返回总数,你必须返回一些让reduce函数工作的东西,你需要总是仔细检查并确保你实际返回了你想要的值。
工具,提示和参考
- 这篇文章中的所有内容都来自egghead上一个名为Introducing Reduce的精彩视频系列。我非常信任Mykola Bilokonsky,并感谢他对我现在知道的关于在JavaScript中使用Reduce方法的所有知识。我试图用他自己的话来解释大部分他解释为更好地理解每个概念的练习。另外,当我需要记住如何做某件事时,我更容易引用文章,而不是视频。
- MDN Reduce文档中也将total称为
累加器
,了解这一点很重要,因为你在线查看资料的时候会发现大多数人都会将它称为累加器,有些人把它称为prev
因为它是之前的值,这都是同样的事物。当我在学习reduce的时候,我发现这样思考total
会比较容易。 - 如果你想练习使用reduce,我建议你注册freeCodeCamp,并使用reduce来完成尽可能多的中间算法。
- 如果你之前没有接触过示例代码片段中的
const
变量,我写了另一篇关于ES6变量的文章,以及你为什么要使用它。 - 我还写了篇名叫The With With Loop的文章,解释如何使用map()和filter()。(如果这些知识对你来说是新的)。