在JavaScript函数式编程里使用Map和Reduce方法
所有人都谈论道workflows支持ECMAScript6里出现的令人吃惊的新特性,因此我们很容易忘掉ECMAScript5带给我们一些很棒的工具方法来支持在JavaScript里进行函数编程,这些工具方法我们现在可以使用了。在这些函数方法里主要的是基于JavaScript 数组对象的map()方法和reduce()方法。
如果你如今还没有使用map()和reduce()方法,那么现在是时候开始使用了。如今绝大部分的JavaScript开发平台都与生俱来的支持ECMAScript5。使用Map方法和reduce方法可以使你的代码更加简洁和更容易阅读和更容易维护,而且把你带到朝向更简洁的功能开发的路上。
性能:一个警告
当然,当现实状况需要保持提高性能时,你的代码的易读性和易维护性不得不在两者之间保持平衡。如今的浏览器使用更笨重的传统技术例如for循环来执行的更有效率。
我写代码的方式通常是把可读性和可维护性放在编写代码的第一位,然后如果我发现在现实情况里代码运行出现了问题,再去为了提高性能而去优化代码。过早的优化代码是很困难的,而且这种优化会导致后面编写代码很困难。
值得考虑的是在JavaScript引擎里使用诸如map()和reduce()这样的方法可以更好地改善代码的性能,而不是指望将来浏览器能为了改善性能而做出优化。除非我在一个性能问题上碰壁,要不然我更喜欢开心地写代码,然而以防我需要它们我却随时准备着为保持代码的性能而做出调整,尽管这样做使我的代码减少了吸引力。
使用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循环,这个for循环有个内部变量和一个循环变量来初始化我们的for循环。然后我们循环迭代每一个项,直到迭代的数组长度等于animals数组的长度。每一次循环我们都计算迭代项的字符长度,然后把它保存到lengths数组里。
注意:需要讨论的是我们可以把上面的代码写得更简洁些,不需要那个item变量,而直接把animals[count]的长度放到lengths数组里,这样就不需要中间的转换过程了。这样做可以帮我们省一点点代码空间,但是它也使得我们的代码有点不易阅读,即使是这个很简单的例子也一样。相同地,为了使代码更有效率而少些直白,我们可能会使用已知的animals数组长度来通过new Array(animals.length)的方式来初始化我们的lengths数组,然后通过索引而不是使用push方法来插入选项数据长度。这取决于你在现实中准备如何去使用这些代码。
这种实现方式没有任何的技术错误。它在任何标准的JavaScript引擎里都可以用,并且它能很好的完成工作。一旦你知道怎么使用map()方法了,通过这种方法来实现就显得有些笨拙了。
让我展示给你看使用map()方法是如何实现上面的代码的:
var animals = ["cat","dog","fish"];var lengths = animals.map(function(animal) { return animal.length; });console.log(lengths); //[3, 3, 4]
在这个小例子里,我们首先再次为我们的animal类型的数组创建一个animals变量。然而,其他我们需要声明的变量就是lengths变量了,然后我们直接分配它的值到映射一个匿名内联函数到animals数组的每一个元素上的结果中。
那个匿名函数执行在每一个animal 上的的操作,然后返回该animal的字符长度。这样做的结果是,lengths变成了具有与原始animals数组有相同长度的数组,并且该数组包含了每一个字符的长度。
这种方式需要注意的一些事情。首先,它比最初编写的代码更简洁。其次,我们仅需要申明更少的变量。更少的变量意味着在全局命名空间里杂音更少,并且如果相同代码的其他部分使用了相同的变量名就会减少碰撞的机会。最后,我们的变量不可能从头到脚都会改变它们的值。随着你深入函数式编程,你将会欣赏使用常量和不变的变量的优雅的能力,现在开始使用并不算早。
这种实现方式的另一个优点是,我们可以通过在整个代码编写过程中通过将代码放进一个个命名的函数里而简化代码而有机会提升自己编写代码的多功能性。匿名的内联函数看着有些乱,并且使得重复使用代码更困难。我们可以定义一个命名为getLength()的函数,并且像下面的方式来使用:
var animals = ["cat","dog","fish"];function getLength(word) { return word.length; }console.log(animals.map(getLength)); //[3, 3, 4]
看看上面的代码看上去多简洁啊?只是把你处理数据的部分映射一下就使你的代码达到一个全新的功能水平。
什么是函子?
有趣的一点是,通过在数组对象里添加映射,ECMAScript5把基本的数组类型变成了一个完整的函子,这使得函数式编程对我们来说更加的容易。
根据传统的函数编程定义,一个函子需要满足三个条件:
1.它保存着一组值。
2.它实现了一个map函数来操作每一个元素。
3.它的map函数返回一个具有同样大小的函子。
这个将会在你下一次的JavaScript聚会上被翻来覆去的讨论。
如果你想了解更多关于函子的信息,你可以看看Mattias Petter Johansson录制的关于这方面的视频。
使用Reduce方法
Reduce()方法在ECMAScript5里也是新增的,而且它和map()方法很类似,除了不产生其他的函子,reduce()方法产生的一个结果是它可能是任何类型。例如,设想一下,如果你想得到我们animals数组里的所有字符长度都分别作为一个数字然后相加的结果。你也许会像下面这样做:
var animals = ["cat","dog","fish"];var total = 0;var item;for (var count = 0, loops = animals.length; count < loops; count++){ item = animals[count]; total += item.length; } console.log(total); //10
在我们定义我们的初始数组之后,我们为运行总计定义了一个total变量,并把它的初始值设为0。我们也定义一个变量item来保存每一次for循环迭代animals数组的迭代值,并且再定义一个count变量作为一个循环计数器,并且用这两个变量来初始化我们的迭代。然后我们运行for循环来迭代animals数组里的所有字符数据,每次迭代都会把迭代的结果保存到item变量。最终我们把每一次迭代到的item的长度加到我们的total变量里就可以了。
这种实现方式也没有任何的技术错误。我们从定义一个数组开始,然后得到一个结果值就结束了。但是如果我们使用了reduce()方法,我们可以使上面的代码更加简单明了:
var animals = ["cat","dog","fish"];var total = animals.reduce(function(sum, word) { return sum + word.length; }, 0);console.log(total);
这里发生变化的是,我们定义了一个名为total的新变量,并且把执行animals数组对象的reduce方法的返回值分配给它,在reduce方法里有两个参数:一个匿名内联function方法,和初始化total的值为0。对于数组中的每一个项reduce()方法都会执行,它在数组的那一项上执行这个function函数,并且把它添加到运行总计,然后再进行下一次迭代。这里我们的内联function方法有两个参数:运行总计,和当前程序正在处理的从数组中获取的字符。这个function函数把当前total的值添加到当前word的长度上。
注意的是:我们设置reduce()方法的第二个参数值为0,这样做确定了total变量包含的是一个数值。如果没有第二个参数reduce方法仍然可以执行,但是执行的结果将不是你期望的那样。(你可以试试并且可以看看当运行总计结束后你是否能推测出JavaScript所使用的编程逻辑。)
那看上去似乎比它需要做的更有一点复杂,因为当调用reduce()方法时需要在一个内联function里综合的定义。我们再那么做一次,但是首先让我们定义一个命名的function函数,而不再使用匿名内联函数:
var animals = ["cat","dog","fish"];var addLength = function(sum, word) { return sum + word.length; };var total = animals.reduce(addLength, 0);console.log(total);
这个代码稍微有点长,但是代码有点长并不总是坏事。这样写你应该看到它使代码更加清晰些,只是在reduce()方法里发生了一点变化。
该程序里reduce()方法有两个参数:一个function函数,用来调用数组里的每一个元素,和一个为total变量设置的运行总计初始值。在这个代码示例中,我们放入了一个名为addLength的新function和为运行总计变量赋初始值为0。在上一行代码中,我们定义了一个名为addLength的function函数,这个函数也需要两个参数:一个当前和一个要处理的字符串。
结论
经常使用map()方法和reduce()方法将会为你的代码更加简洁,更加多功能,更加可维护性提供了选择。同时它们为你使用更多的JavaScript功能函数技术铺平了道路。
map()方法和reduce()方法仅是被添加进ECMAScript5中新方法中的两个。从使用它们的今天你将会明白代码质量的提高和开发人员满意度的提升胜过任何在性能上的临时影响。在判断map()方法和reduce()方法是否适合你的应用之前,试着用功能函数技术来开发和度量一下它在你现实世界里的影响,然后再判断是否去用。
译文链接:http://www.codeceo.com/article/javascript-map-reduce-method.html
英文原文:Using Map and Reduce in Functional JavaScript
翻译作者:码农网 – 唐李川
共同学习,写下你的评论
评论加载中...
作者其他优质文章