核心要点
Array
对象的map()
和reduce()
方法是强大的函数式编程工具,可以使代码更简洁、易读且易于维护。map()
是基本的函数式编程技术,它作用于数组中的所有元素,并生成另一个长度相同的数组,其中包含转换后的内容。通过向数组对象添加映射功能,ECMAScript 5 将基本的数组类型变成了一个完整的函子,使函数式编程更容易上手。reduce()
方法(也是ECMAScript 5中的新增方法)类似于map()
,但它不是生成另一个函子,而是生成单个结果,该结果可以是任何类型。它按顺序将函数应用于数组的每个元素,以便将数组简化为单个输出值。map()
和reduce()
方法可能会影响性能,但如今使用它们带来的代码质量和开发人员满意度的提升,很可能超过对性能的任何暂时影响。作者建议使用函数式技术进行开发,并在实际情况下衡量影响,然后再决定map()
和reduce()
是否适合某个应用程序。ECMAScript 6 的众多令人惊叹的新特性相关的流程讨论甚嚣尘上,很容易让人忘记 ECMAScript 5 为我们在 JavaScript 中支持函数式编程提供了一些很棒的工具,而这些工具我们今天就可以使用。其中包括基于 JavaScript Array
对象的原生 map()
和 reduce()
方法。
如果您现在还没有使用 map()
和 reduce()
,那么现在就开始吧!大多数现代 JavaScript 平台都原生支持 ECMAScript 5。映射和规约可以使您的代码更简洁、更易于阅读和维护,并引导您走向更优雅的函数式开发。
性能:注意事项
当然,在需要的情况下,必须将代码的阅读和维护与性能相平衡。目前,浏览器使用更繁琐的传统技术(例如for
循环)的效率更高。
我的方法通常是首先编写易于阅读和维护的代码,然后如果我在实际情况下注意到问题,则优化性能。过早优化是万恶之源。
还值得考虑的是,随着浏览器对 map()
和 reduce()
进行优化,使用这些方法可能会更好地利用 JavaScript 引擎的改进。除非我面临性能问题,否则我更喜欢乐观地编写代码,并将使我的代码不那么吸引人的性能调整放在我的后备口袋里,以防我需要它们。
使用 Map
映射是作用于数组中所有元素并生成另一个长度相同的数组(包含转换后的内容)的基本函数式编程技术。
为了更具体一些,让我们想出一个简单的用例。例如,假设您有一个单词数组,您需要将其转换为包含每个单词长度的数组。(我知道,这并不是您通常需要为复杂的应用程序执行的那种复杂的火箭科学,但是理解它在这种简单情况下的工作原理将帮助您在它可以为您的代码增加实际价值的情况下应用它)。
您可能已经知道如何使用数组上的for
循环来完成我刚才描述的操作。它可能看起来像这样:
var animals = ["cat","dog","fish"]; var lengths = []; var item; var count; var loops = animals.length; for (count = 0; count < loops; count++) { item = animals[count]; lengths.push(item.length); } console.log(lengths); //[3, 3, 4]
我们所做的只是定义了一些变量:一个名为 animals
的数组,其中包含我们的单词;一个名为 lengths
的空数组,它将包含我们操作的输出;以及一个名为 item
的变量,用于临时存储我们将要在数组的每个循环中操作的每个项目。我们使用一个带有临时内部计数器变量的for
循环和一个loops
变量来优化我们的for
循环。然后,我们迭代了直到 animals
数组长度的每个项目。对于每一个项目,我们计算其长度,并将其推送到 lengths
数组中。
注意:可以说,我们可以通过直接将 animals[count]
的长度推送到 lengths
数组而无需中间赋值来更简洁地完成此操作。这将节省我们一些代码,但也会使代码的可读性降低,即使对于这个非常简单的示例也是如此。同样,为了使它更有效率,但不太直接,我们可以使用已知的 animals
数组长度来初始化我们的 lengths
数组为 new Array(animals.length)
,然后通过索引插入项目而不是使用 push
。这完全取决于您将在现实世界中如何使用代码。
这种方法在技术上没有任何问题。它应该在任何标准的 JavaScript 引擎中都能工作,并且它将完成工作。但是,一旦您知道如何使用 map()
,这样做就会显得笨拙。
让我向您展示我们如何使用 map()
来处理这个问题:
var animals = ["cat","dog","fish"]; var lengths = animals.map(function(animal) { return animal.length; }); console.log(lengths); //[3, 3, 4]
在这种情况下,我们再次从 animals
数组变量开始。但是,我们声明的唯一其他变量是 lengths
,我们将它的值直接赋值给将匿名内联函数映射到 animals
数组的每个元素的结果。该匿名函数对每个动物执行操作,返回长度。结果,lengths
成为与原始 animals
数组长度相同的数组,包含每个单词的长度。
关于这种方法需要注意几点。首先,它比原来的方法短得多。其次,我们必须声明的变量要少得多。变量越少,全局命名空间中的噪声就越少,如果同一代码的其他部分使用相同名称的变量,则冲突的机会就越少。此外,我们的变量从头到尾都不需要更改其值。当您深入研究函数式编程时,您将欣赏使用常量和不可变变量的优雅能力,而且现在就开始学习也为时不晚。
这种方法的另一个优点是,我们有机会通过分离命名函数来提高其多功能性,从而在过程中生成更简洁的代码。匿名内联函数可能看起来很凌乱,并且使代码重用变得更加困难。我们可以定义一个名为 getLength()
的函数,并通过这种方式在上下文中使用它:
var animals = ["cat","dog","fish"]; var lengths = []; var item; var count; var loops = animals.length; for (count = 0; count < loops; count++) { item = animals[count]; lengths.push(item.length); } console.log(lengths); //[3, 3, 4]
看看这看起来多么干净?仅仅将映射作为您的工具包的一部分就可以将您的代码提升到一个全新的函数式级别。
有趣的是,通过向数组对象添加映射功能,ECMAScript 5 将基本的数组类型变成了一个完整的函子,使函数式编程更容易为我们所有人所用。
根据经典的函数式编程定义,函子满足三个条件:
这是在您下次 JavaScript 聚会上可以讨论的一个话题。
如果您想了解更多关于函子的信息,请查看 Mattias Petter Johansson 的这个精彩视频。
使用 Reduce
reduce()
方法也是 ECMAScript 5 中的新增方法,它类似于 map()
,不同之处在于它不是生成另一个函子,而是生成单个结果,该结果可以是任何类型。例如,假设您想将 animals
数组中所有单词的长度之和作为一个数字。您可能会从以下操作开始:
var animals = ["cat","dog","fish"]; var lengths = animals.map(function(animal) { return animal.length; }); console.log(lengths); //[3, 3, 4]
在定义初始数组后,我们为运行总计创建一个变量 total
,最初设置为零。我们还创建了一个变量 item
来保存 animals
数组在遍历for
循环时的每次迭代,以及一个变量 count
用于循环计数器,以及一个 loops
变量来优化我们的迭代。然后,我们运行一个for
循环来迭代 animals
数组中的所有单词,并将每个单词赋值给 item
变量。最后,我们将每个项目的长度添加到我们的总计中。
同样,这种方法在技术上没有任何问题。我们从一个数组开始,最终得到一个结果。但是,使用 reduce()
方法,我们可以使这个过程更直接:
var animals = ["cat","dog","fish"]; var lengths = []; var item; var count; var loops = animals.length; for (count = 0; count < loops; count++) { item = animals[count]; lengths.push(item.length); } console.log(lengths); //[3, 3, 4]
这里发生的情况是,我们正在定义一个新变量 total
,并将其赋值给使用两个参数减少 animals
数组的结果:一个匿名内联函数和一个初始运行总计值零。减少会遍历数组中的每个项目,对该项目执行函数,并将其添加到传递给下一次迭代的运行总计中。这里我们的内联函数有两个参数:运行总和和当前正在从数组中处理的单词。该函数将 total
的当前值添加到当前单词的长度。
请注意,我们将 reduce()
的第二个参数设置为零,这表明 total
将包含一个数字。如果没有第二个参数,reduce
方法仍然可以工作,但结果不一定是您期望的结果。(试一试,看看当省略运行总计时,JavaScript 使用的逻辑是什么。)
由于在调用 reduce()
方法时集成了匿名内联函数的定义,这可能看起来比需要复杂一点。让我们再次这样做,但让我们首先定义一个命名函数,而不是使用匿名内联函数:
var animals = ["cat","dog","fish"]; var lengths = animals.map(function(animal) { return animal.length; }); console.log(lengths); //[3, 3, 4]
这个稍微长一些,但长并不总是坏事。以这种方式查看它应该使 reduce
方法发生的事情更清晰一些。
reduce()
方法有两个参数:一个应用于数组中每个元素的函数,以及一个用于运行总计的初始值。在这种情况下,我们传递一个名为 addLength
的新函数的名称和运行总计的初始值零。我们创建了 addLength()
函数,以便它也接受两个参数:运行总和和要处理的字符串。
结论
养成定期使用 map()
和 reduce()
的习惯将为您提供替代方法,使您的代码更简洁、更通用、更易于维护,并为您铺平使用更多函数式 JavaScript 技术的道路。
map()
和 reduce()
方法只是添加到 ECMAScript 5 的新方法中的两个。很可能,您今天使用它们所看到的代码质量和开发人员满意度的提高将远远超过对性能的任何暂时影响。使用函数式技术进行开发,并在现实世界中衡量影响,然后再决定 map()
和 reduce()
是否适合您的应用程序。
本文由 Panayiotis Velisarakos、Tim Severien 和 Dan Prince 共同评审。感谢所有 SitePoint 的同行评审者,使 SitePoint 内容达到最佳状态!
关于函数式 JavaScript 中 Map-Reduce 的常见问题 (FAQ)
在 JavaScript 中,Map 和 Reduce 都是作用于数组的高阶函数。Map 函数用于通过将函数应用于原始数组的每个元素来创建一个新数组。它不会修改原始数组,而是返回一个新数组。另一方面,Reduce 函数用于将数组简化为单个值。它按顺序将函数应用于数组的每个元素,以便将其简化为单个输出值。
JavaScript 中的 Map 函数通过从现有数组创建一个新数组来工作。它通过将指定的函数应用于原始数组中的每个元素来实现此目的。该函数按顺序为数组中的每个元素调用一次。结果是一个包含函数调用结果的新数组。
JavaScript 中的 Reduce 函数通过按顺序将函数应用于数组中的每个元素,以便将数组简化为单个输出值来工作。输出值是函数调用的累积结果。该函数接受两个参数:累加器和当前值。累加器累积函数调用的返回值。
是的,您可以在 JavaScript 中同时使用 Map 和 Reduce。事实上,它们经常在函数式编程中一起使用。您可以使用 Map 函数转换数组中的每个元素,然后使用 Reduce 函数将转换后的元素组合成单个输出值。
Map 和 ForEach 都是 JavaScript 中作用于数组的高阶函数。它们之间的主要区别在于,Map 通过将函数应用于原始数组的每个元素来创建一个新数组,而 ForEach 应用于数组的每个元素以获得其副作用。ForEach 不会返回新数组。
您可以使用 JavaScript 中的 Map 函数通过将函数应用于数组的每个元素来转换数组。该函数按顺序为数组中的每个元素调用一次。结果是一个包含函数调用结果的新数组。
您可以使用 JavaScript 中的 Reduce 函数将数组的元素组合成单个输出值。该函数按顺序应用于数组中的每个元素,输出值是函数调用的累积结果。
JavaScript 中的 Map 函数通常用于通过将函数应用于数组的每个元素来转换数组。一些常见用例包括将字符串转换为数字、更改字符串的大小写以及从对象中提取属性。
JavaScript 中的 Reduce 函数通常用于将数组的元素组合成单个输出值。一些常见用例包括求和数字、查找最大值或最小值以及连接字符串。
您可以通过在函数中使用 console.log
语句来显示变量和表达式的值来调试 JavaScript 中的 Map 或 Reduce 函数。您还可以使用 debugger
语句暂停执行并在 Web 浏览器的开发者工具中检查变量和表达式的值。
以上是使用地图并减少功能性JavaScript的详细内容。更多信息请关注PHP中文网其他相关文章!