没有循环的JavaScript
有些文章中提到过,缩进(并不能特别准确的)说明了代码的复杂程度。我们想要的是简单的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的更多相关文章
- 622.设计循环队列 javascript实现
设计你的循环队列实现. 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环.它也被称为“环形缓冲器”. 循环队列的一个好处是我们可以利用这个队列 ...
- Javascript中while和do-while循环用法详解
while循环 while 语句与 if 语句相似,都有条件来控制语句(或语句块)的执行,其语言结构基本相同:while(conditions){ statements;} while 语句与 ...
- Javascript语法,变量类型,条件,循环语句,函数,面向对象
1.JavaScript代码革两种存在形式: <!-- 方式一 --> <script type='txt/javascript' src='/js/comment.js'>& ...
- javascript中常见的几种循环遍历
项目开发中,不管是建立在哪个框架基础上,对数据的处理都是必须的,而处理数据离不开各种遍历循环.javascript中循环遍历有很多种方式,记录下几种常见的js循环遍历. 一.for循环 for循环应该 ...
- 一文梳理JavaScript 事件循环(Event Loop)
事件循环(Event Loop),是每个JS开发者都会接触到的概念,但是刚接触时可能会存在各种疑惑. 众所周知,JS是单线程的,即同一时间只能运行一个任务.一般情况下这不会引发问题,但是如果我们有一个 ...
- JavaScript基础
JavaScript基础 JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出相应的处 ...
- 史上最全、JavaScript基础篇
本章内容: 简介 定义 注释 引入文件 变量 运算符 算术运算符 比较运算符 逻辑运算符 数据类型 数字 字符串 布尔类型 数组 Math 语句 条件语句(if.switch) 循环语句(for.fo ...
- 深入理解JavaScript运行机制
深入理解JavaScript运行机制 前言 本文是写作在给团队新人培训之际,所以其实本文的受众是对JavaScript的运行机制不了解或了解起来有困难的小伙伴.也就是说,其实真正的原理和本文阐述的并不 ...
- 15-前端开发之JavaScript
什么是 JavaScript ? JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出 ...
随机推荐
- 设计模式11---组合模式(Composite Pattern)
一.组合模式定义 将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性.Compose objects into tree structures to re ...
- AC620教程 第十五节 8位7段数码管驱动设计与验证
本章导读 电子系统中常用的显示设备有数码管.LCD液晶以及VGA显示器等.其中数码管又可分为段式显示(7段.米字型等)以及点阵显示(8*8.16*16等),LCD液晶的应用可以分为字符式液晶(1602 ...
- 虚拟化技术KVM
1>虚拟化技术: 计算机虚拟化技术是多种技术的综合实现,它包括硬件平台,操作系统,存储以及网络等,简单地说,虚拟化技术就是在单台主机上可以虚拟多个虚假主机,并可以在这些虚拟主机上运行不同的操作系 ...
- add以及update
const addressData = { name: this.post('name'), mobile: this.post('mobile'), province_id: this.post(' ...
- jira项目管理平台搭建
参考文档:http://www.cnblogs.com/ilanni/p/6200875.html 一.环境准备 jira7.2的运行是需要依赖java环境的,也就是说需要安装jdk并且要是1.8 ...
- vmware中安装centos 6.7
centos 6.7 软件下载地址:http://b.mirrors.lanunion.org/CentOS/6.7/isos/i386/ 引用:http://www.cnblogs.com/sees ...
- Maven打包jar项目
默认情况下,使用maven打包的jar项目(执行maven install)不会包含其他包引用,要想打包为带其他项目引用的jar,需要加入插件 要得到一个可以直接在命令行通过java命令运行的JAR文 ...
- Tomcat负载均衡原理详解及配置(Apache2.2.19+Tomcat7.0.12)
结构图 JAVA项目一般直接用Tomcat作为Web服务器.为了增加tomcat的性能和稳定性,我们一般采用balance和session同步机制. 下图列出了我们常用也是最简单的解决方案. 说明 1 ...
- C#多线程编程实战1.1创建线程
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...
- Linux--多用户登录服务器端口抓包
以root身份登录1.新建用户组用命令groupadd test2.添加用户useradd -d /home/test/bei_1 -s /bin/sh -g test -m bei_1此命令新建了一 ...