原文链接:yazeedb.com/posts/learn…
原作者:Yazeed Bzadough
翻译者:发呆小贤
希望这可以减少你的困惑
根据我学习和教学JavaScript的经验,reduce
是最难掌握的概念之一。在这篇文章里,我将尝试解决一个核心问题...
reduce
是什么,为什么它叫reduce
reduce有很多名字
根据 Wikipedia, 其中一些包括
- Reduce
- Fold
- Accumulate
- Aggregate
- Compress
它们都暗示了核心思想。将结构分解为单个值。
Reduce
- 一个可以将列表折叠成任意数据类型的函数
这就像折叠一个盒子!使用reduce
你可以将数组[1,2,3,4,5]
每一项相加得到数字15
。
新手方式
通常,你需要循环才能将列表”折叠“成数字
const add = (x, y) => x + y;
const numbers = [1, 2, 3, 4, 5];
let total = 0;
for(let i = 0; i < numbers.length; i++) {
total = add(total, numbers[i]);
}
console.log(total); // 15
老司机方式
但是使用reduce
你可以插入你的add
函数,就可以为你处理循环了
const add = (x, y) => x + y;
const numbers = [1, 2, 3, 4, 5];
const total = numbers.reduce(add);
// 15
你逐项地折叠1-5即可获得15
三巨头
在深入探讨之前,我认为先分析reduce
和它著名的同伴-map
和filter
很重要。它们严重掩盖了reduce
的光辉,让它看起来很古怪。
尽管它们各自很受欢迎,但结合使用这三个巨人可以让你随意操作列表!
这里我们做个假设,假装JavaScript不能使用循环,递归,或者像forEach
,some
,find
等数组方法。只剩下map
,filter
和reduce
三个方法。
但是我们作为程序员的工作没有改变,我们的应用程序中仍然需要三种类型的功能。
- 转换列表
- 筛选列表
- 将列表转换为其它数据类型(数字,字符串,布尔值,对象等)
让我们看看我们剩下的工具-map
,filter
,reduce
-如何应对这个挑战。
Array.map 转换列表
简而言之,将列表转换成其他列表是前端开发。因此map
涵盖了你的大部分列表工作。
假设我们的应用程序调用了一个用户列表的API,并且我们需要在屏幕上显示每个用户的用户名。只需创建一个返回用户用户名的函数。
const getUserName = (user) => user.name;
然后插入map
针对整个列表列表运行。
users.map(getUserName);
// ['Marie', 'Ken', 'Sara', 'Geoff', ...]
Array.filter 判断列表
如果你想删除某些项而生成一个新列表,例如用户搜索他们的联系人列表?只需创建一个基于它的输入返回true
或false
的函数。
const isEven = (x) => x % 2 === 0;
然后插入filter
针对整个列表运行。
const numbers = [1, 2, 3, 4, 5];
numbers.filter(isEven);
// [2, 4]
Array.reduce 完成所有这些工作,甚至更多
当map
和filter
还不够时,你需要大家伙。map
/filter
能做的工作,reduce
都能做,还有其他任何涉及遍历数组的。
例如,你将如何计算用户的总年龄?我们的用户的年龄是25,22,29和30。
const users = [
{ name: 'Marie', age: 25 },
{ name: 'Ken', age: 22 },
{ name: 'Sara', age: 29 },
{ name: 'Geoff', age: 30 }
];
map
和filter
只能返回数组,但是我们需要的是一个number
!
users.map(?);
users.filter(?);
// Nope! I need a number, not arrays.
如果我们有循环,我们将遍历用户列表并通过一个计数器统计他们的年龄!
好吧,如果我告诉你使用reduce
会更简单些?
users.reduce((total, currentUser) => total + currentUser.age, 0);
// 106
打印下log
我认为要理解它一个简单的方法就是每一步都用console.log
打印出来
const users = [
{ name: 'Marie', age: 25 },
{ name: 'Ken', age: 22 },
{ name: 'Sara', age: 29 },
{ name: 'Geoff', age: 30 }
];
const reducer = (total, currentUser) => {
console.log('current total:', total);
console.log('currentUser:', currentUser);
// just for spacing
console.log('\n');
return total + currentUser.age;
};
users.reduce(reducer, 0);
这里有一张Chrome开发工具的截图。
分解
如你所见,Array.reduce
有两个参数
- reducer
- 初始值(可选)
reducer是完成所有工作的函数。当reduce
循环遍历你的列表时,它提供两个参数给你的reducer
- 累加器
- 当前值
当前值不用说,就像你在常规循环中使用array[i]
一样。不过,累加器是一个听起来很吓人的计算机科学术语,实际上很简单。
累加器是最终返回的值
当你循环遍历用户列表时,你如何跟踪它们的总年龄?你需要一些计数器变量来保存它。那就是累加器。它将作为最终值在reduce
完成后输出。
在循环的每一步,它提供最后的累加器和当前值给你的reducer。无论reducer返回什么都将成为下一个累加器。当列表遍历完成并且你得到一个reduce后的单一的值, 循环结束。
初始值是可选的
reduce
的第二个参数初始值是可选的。如果不提供,默认为列表的第一个元素。
如果你要对纯数字求和,那很好。
[1, 2, 3].reduce((total, current) => total + current);
// 6
但是如果你使用对象或数组会中断,因为你不应该将这些东西加起来。
[{ age: 1 }, { age: 2 }, { age: 3 }].reduce((total, obj) => total + obj.age, 0);
// 6
// Initial value fixes it.
// 0 + 1 + 2 + 3 = 6
让我们重新创建一个reduce
我不能理解我无法创造的东西-理查德·费曼(Richard Feynman)
希望到目前为止,我已经对你有所帮助。现在是时候编写自己的reduce
函数来真正锤炼自己了。
它将是一个有三个参数的函数。
- reducer
- 初始值
- 要操作的数组
对于这个demo,初始值不是可选的
const reduce = (reducer, initialValue, array) => {
let accumulator = initialValue;
for (let i = 0; i < array.length; i++) {
const currentItem = array[i];
accumulator = reducer(accumulator, currentItem);
}
return accumulator;
};
仅需10行代码,6个关键步骤。我将一步一步说明。
- 定义
reduce
及其三个参数。 - 使用接收到的初始值参数初始化初始值(
initialValue
)。该变量每个循环都将改变。 - 开始遍历数组。
- 捕获循环中数组的当前值(
currentItem
)。 - 调用
reducer
并将accumulator
和currentItem
传入,将结果作为新的accumulator
保存。 - 当循环结束并且
accumulator
更改完成,将其返回。
多样的历史
我想谈谈更多关于 reduce
和 reducers
的历史,但不大确定应该放在哪里。尽管如此,它还是很有趣的!
reducers是古老的
Redux为JavaScript开发人员带来了reducers
的炫酷用法。但它没有发明他们。实际上不清楚是谁创造了该术语,但这里是我参考的一些参考文献。
递归理论(1952)
1952年出版的 这本书 从形而上学的角度讨论了reduce
,将其称为fold。
Lisp程序员手册(1960)
1960出版的《Lisp程序员手册》中有一章是关于reduce
函数的。
函数式编程概论(1988)
1988年出版的 这本书 谈论如何使用 reduce
将列表转换为其他值。
底线是一个古老的话题。你对计算机科学的研究越多,你就越意识到我们正在重新包装几十年前发现的概念。
你对计算机科学的研究越多,你就越意识到我们正在重新包装几十年前发现的概念。 — Yazeed Bzadough (@yazeedBee) October 13, 2019
为你准备的练习
为了节省时间,我们就在这里结束。我希望至少已暗示这reduce
除求和之外的强大功能。
如果您有兴趣,请尝试这些练习(尽请关注后续文章)
- 使用
reduce
重新实现 Array.map 函数。 - 使用
reduce
重新实现 Array.filter 函数。 - 使用
reduce
重新实现 Array.some 函数。 - 使用
reduce
重新实现 Array.every 函数。 - 使用
reduce
重新实现 Array.find 函数。 - 使用
reduce
重新实现 Array.forEach 函数。 - 使用
reduce
将数组转换为对象。 - 使用
reduce
将二维数组转换为一维数组(flat)。
传送门
【译】使用reduce制作的10个JavaScript实用函数