有些文章中提到过,缩进(并不能特别准确的)说明了代码的复杂程度。咱们想要的是简单的JavaScript。之因此层层缩进,是由于咱们用抽象的方式解决问题。但要选用什么抽象方法呢?截止目前,咱们没有在特定环境中说明该使用什么样的方法。本文将关注如何在摆脱循环的状况下使用数组。最终的结果固然是更简单可读的代码。api
“……循环是个不可避免的结构,并且很差复用,同时循环还很难加入其余操做中。更麻烦的是,使用循环就意味着在每个新的迭代中有更多变化须要响应”——Luis Atencio数组
循环浏览器
相似循环同样的控制结构会让代码变得复杂。但目前并无什么证据能证实它。如今让咱们看看JavaScript中的循环是如何工做的。app
在JavaScript中,咱们至少有4到5种循环的方法。最基本的要数while循环。开始以前咱们先写一个示例函数和数组方便咱们说明:函数
如今咱们有了一个数组,来用oodify处理它。当咱们使用while时,循环应该这样写:性能
请注意,为了知道咱们所在的位置,我用了计数器i。首先将计数器初始化清零,以后在每次循环中加1计数。同时,咱们还要比较i和数组长度,这样才能知道何时中止循环。JavaScript提供另一个和它差很少,并且更简单的写法:for循环。用for循环能够这样写:ui
for循环是个颇有用的结构,由于它能够将计数器的逻辑都包含在顶部,这是个很不错的改进。当咱们使用while循环时,很容易就忘了写i的加1计数,而后形成无限循环。如今咱们来看看这段代码的做用。咱们试图对数组中的每一个元素调用oodlify(),而后将结果存入新的数组。事实上咱们不太想本身操做计数器。3d
这种对每个数组元素作处理的方式十分常见。因此在ES2015中,提供了一个能够不用在乎计数器的新的循环结构:for…of循环。每一轮循环它都会将数组中的下一个元素传给你。它看上去是这样的:对象
这个方法看上去简单不少。能够注意到计数器以及比较数组长度的过程都不见了。咱们甚至不用本身将元素从数组中取出。for…of循环干了全部脏活累活。若是咱们用for…of循环替代全部的for循环,就会取得很大进步。如今咱们已经让代码变得更简单,但咱们的目标不止如此。blog
映射(Mapping)
for…of循环要比for循环简单的多,但仍是须要一些手动配置。首先须要初始化output数组,还要在每层循环中调用push()函数。若是能解决代码中一些现有的问题,还能够将代码变得更清晰明了,其存在的问题是:
若是有两个数组都须要调用oodlify怎么办?
首先想到的应该是对两个数组都用循环:
这当然有用。并且好处大于坏处。但这个方法重复使用了太屡次——不是特别清爽。如今咱们要去除一些重复来将它重构,先写一个函数:
看上去是否是好些了,但若是咱们还有想要的函数怎么办?
这种状况下oodlifyArray()函数就帮不上什么忙了。若是咱们建立一个izzlifyArrya()函数,这就又走了那个不断重复的老路。无论怎样,先试试,咱们好看看究竟是什么效果:
能够看出这两个函数功能惊人的类似。那若是咱们能够将其中的模式抽象出来会怎样?事实上,咱们想要的是:对于给出的数组和函数,将数组中的每一个元素映射到新的数组中。而后把函数做用在每一个元素上。咱们把这种状况称为模式映射。数组的映射函数长这样:
这个方法仍是没有彻底摆脱循环。若是咱们想要摆脱循环,那就须要写一个递归的版本:
递归的方法好像挺高级。只须要两行代码,没什么缩进。但通常来讲,咱们不怎么用递归的方法,由于它在老版本浏览器上性能不怎么样。并且事实上,咱们并不须要本身去写映射函数(除非你想这么作)。映射函数实际上很是常见,因此JavaScript为咱们提供了一个建立映射的方法。用映射方法,代码看上去是这样的:
注意到这种写法彻底没有缩进。彻底没有循环。事实上,其内部某些地方确实存在循环,但这彻底不是咱们须要关心的。这下代码看上去就很是的简单了。
那为何这样写看上去特别简单呢?这个问题彷佛特别蠢,但请仔细想一想。是由于它特别短吗?答案是否认的。仅仅由于代码量少并不表明它简单。它看起来简单是由于咱们把他们分开了。有两个函数来处理字符串:oofligy和izzlify。这些函数与数组或循环无关。另外一个函数map会处理数组。但map不会管数组中的数据是什么类型,或你想用这些数据干什么。它只是在调用咱们传给他的函数。和把全部东西混在一块不同,咱们将字符串的处理过程和数组的处理分开。这就是代码简单的缘由。
精简(Reducing)
如今map这个函数很是便利,但它没办法覆盖咱们须要的全部循环。它只在你想要建立一个和输入同样长的数组才有用。那若是咱们想增长数组元素数量怎么办?或是想要在列表中找出最短的字符串。还有些时候咱们想处理一个数组或将其元素减至一个。
如今来看个例子。假如咱们有一个英雄对象的数组:
咱们想找到最强壮的英雄。使用for循环,过程是这样:
代码看上去不错,它将全部事情考虑了进去。当咱们开始循环的时候,始终能够从strongest中获取当前循环中最强壮的英雄。那新的问题来了,假设咱们想知道全部英雄加起来有多强。
两个例子中,咱们在开始循环以前都先初始化了一个变量。而后每一次循环中,都从数组中去取出一个值,而后更新这个变量。为了使循环更加简洁,咱们从循环中提取因子而后使用函数。同时,咱们将从新命名一些变量。
这样写的话,两个循环看上去就很是相近。二者的区别仅仅存在于函数名和初始值上。两个方法都将数组的元素减至一个。于是咱们能够建立一个reduce函数来继续压缩这个模式。
JavaScript在reduce上为数组提供一种如map同样内嵌的方法。这样咱们就不用本身写相关的方法了。使用内嵌的方法,代码应该是这样的:
若是你们有仔细阅读本文,你应该发现这段代码并非最短的。使用数组内嵌的方法,咱们也就减小了一行代码。但咱们的目标是尽可能减小函数的复杂性,而不是追求更少的代码量。那这样写到底有没有减小复杂性呢?答案是确定的。将代码从独立处理元素的过程当中分离,令其单独处理循环。这样代码就减小了一些复杂性。
过滤(Filtering)
咱们首先使用map能够对数组中的每一个元素进行操做。同时咱们用reduce将数组减至一个元素。那若是咱们想从数组中提取某些元素该怎么办?为了继续研究,来稍微扩充一下咱们以前的数据:
如今面前有两个目标:
找到全部女英雄;
找到那些力量值大于500的英雄
先用老方法for循环,能够这样写:
这段代码挺不错的,它考虑到了全部的内容。但其中绝对有一些重复的模式。事实上,惟一改变的就是那个if条件申明。那如今将if拿出来单独做为函数。
这个的函数只会返回true或false,它有时被称为predicate。咱们使用predicate来决定到底要不要保留heroes中的元素。
这样的写法让代码变得更长了。若是咱们将predicate函数提取出来,提取后的版本变得清晰无比。咱们用提取的部分建立函数。
和map,reduce同样,JavaScript为咱们提供的也是数组方法。因此咱们不用来本身多写什么(除非你想要这样作)。使用数组方法,代码变成了:
为何这样比使用for循环强太多?思考一下咱们是如何在例子中使用的。咱们最初的问题是如何找出符合条件的英雄。当咱们用filter函数解决了这个问题后,剩下的工做轻松无比。咱们写了一个简单的函数,用它告诉filter函数哪些元素须要保留。最后咱们写了一个很是简单的predicate函数,就不用考虑数组或变量了。
与其余方法相比,使用filter能传递更多信息,而且使用了更少的空间。彻底不必熟悉全部循环后来实现过滤。只须要写一个方法调用便可。
查找(Finding)
Filtering用起来很方便,但若是咱们只想找一位英雄呢?假设咱们要找到Black Widow。当使用filter函数:
这样写效率不高。filter须要查看数组中的每一个元素。但咱们知道只有一个Black Widow,彻底能够在找到一个Black Widow后结束查找。predicate函数的用法是很是灵活的。咱们能够来写一个find函数以返回匹配到的第一个元素。
和以前同样,JavaScript能够包办所有,咱们不用本身建立什么复杂函数:
最终咱们用较少的文字表达了更多内容。用find函数解决了以前查找特定元素的问题,如今有一个新的疑问:我怎么知道是找到特定的元素就结束仍是遍历整个数组。然而这并非咱们要关心的内容!
小结:
从这些迭代函数中不难看出抽象思惟的价值。假设咱们用内嵌数组的方法处理一切问题。在每一个案例中咱们都完成了三件事:
剔除循环控制结构,加强代码可读性;
用现有的方法来概括例子中的模式;
明确咱们到底要对数组中的元素作什么操做。
在每一个例子中,咱们都用小而纯粹的函数将问题分解。真正重要的就是这四种模式(也有其余方法,但我推荐这四种),用它们你几乎能够淘汰JavaScript中全部的循环了。这是由于几乎JavaScript中全部的循环都是来处理数组,或建立数组的。在减小循环的过程当中,咱们不但减小了代码的复杂性,同时也加强了代码的可维护性。