【译】10分钟学会reduce

1,211 阅读7分钟

原文链接: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

folding-box

新手方式

通常,你需要循环才能将列表”折叠“成数字

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

folding-box

三巨头

在深入探讨之前,我认为先分析reduce和它著名的同伴-mapfilter很重要。它们严重掩盖了reduce的光辉,让它看起来很古怪。

creepy-reduce

尽管它们各自很受欢迎,但结合使用这三个巨人可以让你随意操作列表!

the-big-three

这里我们做个假设,假装JavaScript不能使用循环,递归,或者像forEachsomefind等数组方法。只剩下mapfilterreduce三个方法。 但是我们作为程序员的工作没有改变,我们的应用程序中仍然需要三种类型的功能。

  1. 转换列表
  2. 筛选列表
  3. 将列表转换为其它数据类型(数字,字符串,布尔值,对象等)

让我们看看我们剩下的工具-map,filterreduce-如何应对这个挑战。

Array.map 转换列表

简而言之,将列表转换成其他列表是前端开发。因此map涵盖了你的大部分列表工作。

假设我们的应用程序调用了一个用户列表的API,并且我们需要在屏幕上显示每个用户的用户名。只需创建一个返回用户用户名的函数。

const getUserName = (user) => user.name;

然后插入map针对整个列表列表运行。

users.map(getUserName);
// ['Marie', 'Ken', 'Sara', 'Geoff', ...]

Array.filter 判断列表

如果你想删除某些项而生成一个新列表,例如用户搜索他们的联系人列表?只需创建一个基于它的输入返回truefalse的函数。

const isEven = (x) => x % 2 === 0;

然后插入filter针对整个列表运行。

const numbers = [1, 2, 3, 4, 5];
numbers.filter(isEven);
// [2, 4]

Array.reduce 完成所有这些工作,甚至更多

mapfilter还不够时,你需要大家伙。map/filter能做的工作,reduce都能做,还有其他任何涉及遍历数组的。

reduce-will-take-this

例如,你将如何计算用户的总年龄?我们的用户的年龄是25,22,29和30。

const users = [
  { name: 'Marie', age: 25 },
  { name: 'Ken', age: 22 },
  { name: 'Sara', age: 29 },
  { name: 'Geoff', age: 30 }
];

mapfilter只能返回数组,但是我们需要的是一个number

users.map(?);
users.filter(?);

// Nope! I need a number, not arrays.

如果我们有循环,我们将遍历用户列表并通过一个计数器统计他们的年龄! 好吧,如果我告诉你使用reduce会更简单些?

users.reduce((total, currentUser) => total + currentUser.age, 0);
// 106

fallout-hold-up

打印下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开发工具的截图。

reduce-screenshot-1

分解

如你所见,Array.reduce有两个参数

  1. reducer
  2. 初始值(可选)

reducer是完成所有工作的函数。当reduce循环遍历你的列表时,它提供两个参数给你的reducer

  1. 累加器
  2. 当前值

当前值不用说,就像你在常规循环中使用array[i]一样。不过,累加器是一个听起来很吓人的计算机科学术语,实际上很简单。

累加器是最终返回的值

当你循环遍历用户列表时,你如何跟踪它们的总年龄?你需要一些计数器变量来保存它。那就是累加器。它将作为最终值在reduce完成后输出。

在循环的每一步,它提供最后的累加器和当前值给你的reducer。无论reducer返回什么都将成为下一个累加器。当列表遍历完成并且你得到一个reduce后的单一的值, 循环结束。

reduce-screenshot-1

初始值是可选的

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函数来真正锤炼自己了。

它将是一个有三个参数的函数。

  1. reducer
  2. 初始值
  3. 要操作的数组

对于这个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个关键步骤。我将一步一步说明。

  1. 定义reduce及其三个参数。
  2. 使用接收到的初始值参数初始化初始值(initialValue)。该变量每个循环都将改变。
  3. 开始遍历数组。
  4. 捕获循环中数组的当前值(currentItem)。
  5. 调用reducer并将accumulatorcurrentItem传入,将结果作为新的accumulator保存。
  6. 当循环结束并且accumulator更改完成,将其返回。

多样的历史

我想谈谈更多关于 reducereducers 的历史,但不大确定应该放在哪里。尽管如此,它还是很有趣的!

reducers是古老的

redux-did-not-invent-reducers

Redux为JavaScript开发人员带来了reducers的炫酷用法。但它没有发明他们。实际上不清楚是谁创造了该术语,但这里是我参考的一些参考文献。

递归理论(1952)

1952年出版的 这本书 从形而上学的角度讨论了reduce,将其称为fold。

Lisp程序员手册(1960)

1960出版的《Lisp程序员手册》中有一章是关于reduce函数的。

函数式编程概论(1988)

1988年出版的 这本书 谈论如何使用 reduce 将列表转换为其他值。 底线是一个古老的话题。你对计算机科学的研究越多,你就越意识到我们正在重新包装几十年前发现的概念。

你对计算机科学的研究越多,你就越意识到我们正在重新包装几十年前发现的概念。 — Yazeed Bzadough (@yazeedBee) October 13, 2019

为你准备的练习

为了节省时间,我们就在这里结束。我希望至少已暗示这reduce除求和之外的强大功能。

如果您有兴趣,请尝试这些练习(尽请关注后续文章)

  1. 使用 reduce 重新实现 Array.map 函数。
  2. 使用 reduce 重新实现 Array.filter 函数。
  3. 使用 reduce 重新实现 Array.some 函数。
  4. 使用 reduce 重新实现 Array.every 函数。
  5. 使用 reduce 重新实现 Array.find 函数。
  6. 使用 reduce 重新实现 Array.forEach 函数。
  7. 使用 reduce 将数组转换为对象。
  8. 使用 reduce 将二维数组转换为一维数组(flat)。

传送门

【译】使用reduce制作的10个JavaScript实用函数

【译】再来10个使用reduce制作的JavaScript实用函数

【译】还来10个使用reduce制作的JavaScript实用函数