有些文章中提到过,缩进(并不能特别准确的)说明了代码的复杂程度。我们想要的是简单的JavaScript。之所以层层缩进,是因为我们用抽象的方式解决问题。但要选用什么抽象方法呢?截止目前,我们没有在特定环境中说明该使用什么样的方法。本文将关注如何在摆脱循环的情况下使用数组。最终的结果当然是更简单可读的代码。

“……循环是个不可避免的结构,而且不好复用,同时循环还很难加入其他操作中。更麻烦的是,使用循环就意味着在每一个新的迭代中有更多变化需要响应”——Luis Atencio

循环

类似循环一样的控制结构会让代码变得复杂。但目前并没有什么证据能证明它。现在让我们看看JavaScript中的循环是如何工作的。

在JavaScript中,我们至少有4到5种循环的方法。最基本的要数while循环。开始之前我们先写一个示例函数和数组方便我们说明:

现在我们有了一个数组,来用oodify处理它。当我们使用while时,循环应该这样写:

请注意,为了知道我们所在的位置,我用了计数器i。首先将计数器初始化清零,之后在每次循环中加1计数。同时,我们还要比较i和数组长度,这样才能知道什么时候停止循环。JavaScript提供另外一个和它差不多,而且更简单的写法:for循环。用for循环可以这样写:

for循环是个很有用的结构,因为它可以将计数器的逻辑都包含在顶部,这是个很不错的改进。当我们使用while循环时,很容易就忘了写i的加1计数,然后造成无限循环。现在我们来看看这段代码的作用。我们试图对数组中的每个元素调用oodlify(),然后将结果存入新的数组。事实上我们不太想自己操作计数器。

这种对每一个数组元素做处理的方式十分常见。所以在ES2015中,提供了一个可以不用在意计数器的新的循环结构:for…of循环。每一轮循环它都会将数组中的下一个元素传给你。它看上去是这样的:

这个方法看上去简单很多。可以注意到计数器以及比较数组长度的过程都不见了。我们甚至不用自己将元素从数组中取出。for…of循环干了所有脏活累活。如果我们用for…of循环替代所有的for循环,就会取得很大进步。现在我们已经让代码变得更简单,但我们的目标不止如此。

映射(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中所有的循环都是来处理数组,或创建数组的。在减少循环的过程中,我们不但减少了代码的复杂性,同时也增强了代码的可维护性。

没有循环的JavaScript的更多相关文章

  1. 622.设计循环队列 javascript实现

    设计你的循环队列实现. 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环.它也被称为“环形缓冲器”. 循环队列的一个好处是我们可以利用这个队列 ...

  2. Javascript中while和do-while循环用法详解

    while循环 while 语句与 if 语句相似,都有条件来控制语句(或语句块)的执行,其语言结构基本相同:while(conditions){    statements;} while 语句与 ...

  3. Javascript语法,变量类型,条件,循环语句,函数,面向对象

    1.JavaScript代码革两种存在形式: <!-- 方式一 --> <script type='txt/javascript' src='/js/comment.js'>& ...

  4. javascript中常见的几种循环遍历

    项目开发中,不管是建立在哪个框架基础上,对数据的处理都是必须的,而处理数据离不开各种遍历循环.javascript中循环遍历有很多种方式,记录下几种常见的js循环遍历. 一.for循环 for循环应该 ...

  5. 一文梳理JavaScript 事件循环(Event Loop)

    事件循环(Event Loop),是每个JS开发者都会接触到的概念,但是刚接触时可能会存在各种疑惑. 众所周知,JS是单线程的,即同一时间只能运行一个任务.一般情况下这不会引发问题,但是如果我们有一个 ...

  6. JavaScript基础

    JavaScript基础 JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出相应的处 ...

  7. 史上最全、JavaScript基础篇

    本章内容: 简介 定义 注释 引入文件 变量 运算符 算术运算符 比较运算符 逻辑运算符 数据类型 数字 字符串 布尔类型 数组 Math 语句 条件语句(if.switch) 循环语句(for.fo ...

  8. 深入理解JavaScript运行机制

    深入理解JavaScript运行机制 前言 本文是写作在给团队新人培训之际,所以其实本文的受众是对JavaScript的运行机制不了解或了解起来有困难的小伙伴.也就是说,其实真正的原理和本文阐述的并不 ...

  9. 15-前端开发之JavaScript

    什么是 JavaScript ? JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出 ...

随机推荐

  1. 检测远程主机上的某个端口是否开启——telnet命令

    要测试远程主机上的某个端口是否开启,无需使用太复杂的工作,windows下就自带了工具,那就是telnet.ping命令是不能检测端口,只能检测你和相应IP是否能连通. 1 安装telnet.win7 ...

  2. KindEditor上传图片

    <script type="text/javascript"> KindEditor.ready(function(K) { var editor1 = K.creat ...

  3. CGLIB介绍与原理(通过继承的动态代理)

    一.什么是CGLIB? CGLIB是一个功能强大,高性能的代码生成包.它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充.通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接 ...

  4. 设计模式9---装饰模式(Decorator Pattern)

    装饰模式又名包装(Wrapper)模式.装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案. 装饰模式的结构 装饰模式以对客户透明的方式动态地给一个对象附加上一些责任.换言之,客户端 ...

  5. CH的电影推荐

    1.推荐电影 张艺谋:一个都不能少 2.下载站点 TL95

  6. CRC循环冗余校验算法

    现实中通信链路都不会是理想的,比特在传输的过程中可能会出现差错,0变成1,1变成0.这就叫做比特差错.因此为了保证数据传输的可靠性,在计算网络传输数据时,必须采用各种检验措施来处理比特差错.在数据链路 ...

  7. iOS核心动画CALayer和UIView

    UIView和CALayer的关系. 每一个UIview都有一个CALayer实例的图层属性,也就是所谓的backing layer. 实际上这些背后关联的图层才是真正用来在屏幕上显示和做动画,UIV ...

  8. luogu P1080国王游戏

    贪心加高精 传送门:QWQ 先考虑两个人 a0 b0 p1 a1 b1 p2 a2 b2 那么满足:\(\huge ans1=\max(\frac{a0}{b1} , \frac{a0a1}{b2}) ...

  9. 976 AlvinZH想回家(背包DP大作战T)

    976 AlvinZH想回家 思路 如果在第i小时有一些飞机延误,那么一架飞机的c值越大,这一小时产生的损失也越大.而使这一小时产生的损失尽可能的小并不会导致接下来时间产生的损失增大.因此应当每一小时 ...

  10. HTTP请求处理流程-SpringMvc

    1.在SpringMVC的http请求处理过程中,包括了前端控制器(DispatcherServlet).处理映射器(HandlerMapping).处理适配器(HandlerAdapter).处理器 ...