假如你刚入坑JavaScript,可能对 .map().reduce() 和、 .filter() 。对我来说得花一阵子去了解,因为前不久我还得维护IE8。但是,如果不需要兼容低非常古老的浏览器,就需要熟悉这些方法。
请注意本文也可能适用于其他编程语言,因为在其他编程语言中有这些概念。

.map()

举个简单的例子。假设你得到一个包含多个对象的数组——每个对象代表一个人。最后你需要得到一个包含每个人id的数组。

  1. // What you have
  2. var officers = [
  3. { id: 20, name: 'Captain Piett' },
  4. { id: 24, name: 'General Veers' },
  5. { id: 56, name: 'Admiral Ozzel' },
  6. { id: 88, name: 'Commander Jerjerrod' }
  7. ];
  8. // What you need
  9. [20, 24, 56, 88]

有很多方法可以实现。你也许想通过创建一个空数组,然后使用 .forEach() , .for(...of) 或者简单的 .for() 来实现。
让我们来对比一下!
使用.forEach()

  1. var officersIds = [];
  2. officers.forEach(function (officer) {
  3. officersIds.push(officer.id);
  4. });

什么,你需要预先创建一个空数组?让我们看看使用.map() 会是怎么样的:

  1. var officersIds = officers.map(function (officer) {
  2. return officer.id
  3. });

我们甚至使用箭头函数变得更加简洁(需要ES6支持,Babel或者Typescript)

  1. const officersIds = officers.map(officer => officer.id);

.map() 是怎么工作的?通常情况它接受两个参数,一个callback,和一个可选上下文(执行callback被用作this),这个在上述示例中没有使用。数组中每个元素都会被callback函数调用一次,并且回调函数的结果组成了新数组的每一个元素。
需要注意的是结果数组总是和原数组保持相同的长度。

.reduce()

类似 .map().reduce() 也是对数组每个元素执行一个回调函数。不同的是,reduce将callback(累加器)的返回值从一个数组元素传递到另一个数组元素。这个累加器几乎可以是任何类型(整数、字符串、对象等等),并且在调用 .reduce() 必须实例化或传递。

举个例子!假设你有一个数组,它包含一些飞行员及其工龄:

  1. var pilots = [
  2. {
  3. id: 10,
  4. name: "Poe Dameron",
  5. years: 14,
  6. },
  7. {
  8. id: 2,
  9. name: "Temmin 'Snap' Wexley",
  10. years: 30,
  11. },
  12. {
  13. id: 41,
  14. name: "Tallissan Lintra",
  15. years: 16,
  16. },
  17. {
  18. id: 99,
  19. name: "Ello Asty",
  20. years: 22,
  21. }
  22. ];

我们需要了解他们工龄总和。直接用 .reduce() :

  1. var totalYears = pilots.reduce(function (accumulator, pilot) {
  2. return accumulator + pilot.years;
  3. }, 0);

请注意我设置初始值是0。如果需要的话,也可使用一个现有变量作为初始值。reduce在执行callback回调之后将返回累加器最终的值(在例子:82)。
让我们瞧瞧在ES6箭头函数下代码变得更短小精悍:

  1. const totalYears = pilots.reduce((acc, pilot) => acc + pilot.years, 0);

现在假设我想知道哪个飞行员的工龄最大。我也可以像这样使用reduce函数:

  1. var mostExpPilot = pilots.reduce(function (oldest, pilot) {
  2. return (oldest.years || 0) > pilot.years ? oldest : pilot;
  3. }, {});

我将累加器命名为 oldest 。回调累加器将与每个飞行员做比较。如果某个飞行员的工龄大于累加器,那么这个飞行员的工龄就成为新的 oldest
正如你所见,使用.reduce() 从一个数组中生成单个值或者是对象是一件非常容易的事儿。

.filter()

假如有这么一个数组,但仅仅想知道其中是否存在某个元素怎么办?那就是.filter() 登场了!
我们的数组在这儿:

  1. var pilots = [
  2. {
  3. id: 2,
  4. name: "Wedge Antilles",
  5. faction: "Rebels",
  6. },
  7. {
  8. id: 8,
  9. name: "Ciena Ree",
  10. faction: "Empire",
  11. },
  12. {
  13. id: 40,
  14. name: "Iden Versio",
  15. faction: "Empire",
  16. },
  17. {
  18. id: 66,
  19. name: "Thane Kyrell",
  20. faction: "Rebels",
  21. }
  22. ];

假设我们想要两个数组:一个是反叛派系(rebel)飞行员,另一个是帝国派系(empire)飞行员。使用.filter() 简直不要太简单了:

  1. var rebels = pilots.filter(function (pilot) {
  2. return pilot.faction === "Rebels";
  3. });
  4. var empire = pilots.filter(function (pilot) {
  5. return pilot.faction === "Empire";
  6. });

是的!箭头函数甚至更短:

  1. const rebels = pilots.filter(pilot => pilot.faction === "Rebels");
  2. const empire = pilots.filter(pilot => pilot.faction === "Empire");

基本上,如果回调函数返回 true,当前元素就出现在结果数组中。相反如果返回false,则不会。

Combining .map(), .reduce(), and .filter()

由于三个方法都是在数组上调用的,并且.map().filter() 都返回数组,我们可以轻松地使用链式调用。
来看看另一个例子。这是数据:

  1. var personnel = [
  2. {
  3. id: 5,
  4. name: "Luke Skywalker",
  5. pilotingScore: 98,
  6. shootingScore: 56,
  7. isForceUser: true,
  8. },
  9. {
  10. id: 82,
  11. name: "Sabine Wren",
  12. pilotingScore: 73,
  13. shootingScore: 99,
  14. isForceUser: false,
  15. },
  16. {
  17. id: 22,
  18. name: "Zeb Orellios",
  19. pilotingScore: 20,
  20. shootingScore: 59,
  21. isForceUser: false,
  22. },
  23. {
  24. id: 15,
  25. name: "Ezra Bridger",
  26. pilotingScore: 43,
  27. shootingScore: 67,
  28. isForceUser: true,
  29. },
  30. {
  31. id: 11,
  32. name: "Caleb Dume",
  33. pilotingScore: 71,
  34. shootingScore: 85,
  35. isForceUser: true,
  36. },
  37. ];

我们的目标:获取原力用户的总分。让我们一步一步来!
首先,我们需要筛选出不能使用原力(force)的用户:

  1. var jediPersonnel = personnel.filter(function (person) {
  2. return person.isForceUser;
  3. });
  4. // Result: [{...}, {...}, {...}] (Luke, Ezra and Caleb)s

这样我们结果数组中剩下3个元素。我们需要生成一个包含每个绝地武士(Jedi)总分的数组.

  1. var jediScores = jediPersonnel.map(function (jedi) {
  2. return jedi.pilotingScore + jedi.shootingScore;
  3. });
  4. // Result: [154, 110, 156]

然后我们使用 reduce 来获得总分:

  1. var totalJediScore = jediScores.reduce(function (acc, score) {
  2. return acc + score;
  3. }, 0);
  4. // Result: 420

重点来了…我们可以将所有这些调用链接在一起,从而在一行中得到我们想要的东西:

  1. var totalJediScore = personnel
  2. .filter(function (person) {
  3. return person.isForceUser;
  4. })
  5. .map(function (jedi) {
  6. return jedi.pilotingScore + jedi.shootingScore;
  7. })
  8. .reduce(function (acc, score) {
  9. return acc + score;
  10. }, 0);

用了箭头函数之后更棒了:

  1. const totalJediScore = personnel
  2. .filter(person => person.isForceUser)
  3. .map(jedi => jedi.pilotingScore + jedi.shootingScore)
  4. .reduce((acc, score) => acc + score, 0);

Boom! 💥

注意:在上个例子中,.map()和.filter()并不是必需的。我们可以仅用.reduce()达到相同的结果输出。我在例子里留了思考。你能猜想到怎样才能仅用.reduce()一行代码达到相同的结果吗?See the solution on CodePen

Why not use .forEach()?

I used to use for loops everywhere instead of .map(), .reduce(), and .filter(). But a couple of years ago I started working a lot more with data that came from an API. That’s where I began to see the advantages of leaving .forEach behind.
我曾常常使用for循环而不是 .map().reduce().filter() 。但几年前我开始频繁处理从API获取的数据。我就开始不怎么用 .forEach

Formatting

假设你需要显示人员列表,包括其姓名和职务。

  1. var data = [
  2. {
  3. name: "Jan Dodonna",
  4. title: "General",
  5. },
  6. {
  7. name: "Gial Ackbar",
  8. title: "Admiral",
  9. },
  10. ]

以上数据是API给你的,但你仅仅需要每个人的标题title和姓氏last name…你需要格式化数据。然而,你的应用也需要为每个人展示单视图,所以必须写一个列表视图和单视图两不误的数据格式化函数。
那意味着在格式化函数中不能使用 .forEach 循环,否则,你必须将单个元素包装在数组中,然后才能将其传递给函数,以使其正常工作,像这样:

  1. var result = formatElement([element])[0];
  2. // Yeah... that's not right at all

因此循环必须对该函数的调用进行包装,如下所示:

  1. data.forEach(function (element) {
  2. var formatted = formatElement(element);
  3. // But what then....
  4. });

.forEach() 不会返回任何元素。它做不到。这意味着你不得不把结果放入一个预数组中。

  1. var results = [];
  2. data.forEach(function (element) {
  3. var formatted = formatElement(element);
  4. results.push(formatted);
  5. });

结果,你有2个函数: formatElement() 函数和push结果到数组中的函数。
何必多此一举呢?

  1. var results = data.map(formatElement);

Testing is easier

If you write unit tests for your code, you’ll find it simpler to test the functions you call with .map(),
如果你为代码配置了单元测试,你将发现对使用 .map().reduce() 或者 .filter() 的函数进行测试变得更简单了。
你需要做的就是给函数提供数据和等结果出来。基本上是“如果通过了会怎样?”。更少操作,更少 beforeEachafterEach 了。简洁明了的测试。

Try it!

在合适的地方试着用.map().reduce() 或者 .filter() 的代替 for 循环。我保证你的代码看起更加清爽和可读性更佳。
如果你喜欢本文以及想学习更多数组方法,查阅我的文章how to use [.some()](https://medium.com/poka-techblog/simplify-your-javascript-use-some-and-find-f9fb9826ddfd) and [.find()](https://medium.com/poka-techblog/simplify-your-javascript-use-some-and-find-f9fb9826ddfd) in JavaScript.
Keep coding!

原文地址:https://medium.com/poka-techblog/simplify-your-javascript-use-map-reduce-and-filter-bd02c593cc2d 原文作者:Etienne Talbot