循环吧,代码
编程语言可以很迅速方便地帮我们完成一些重复性的任务,从多个基本计算到几乎完成了很多类似工作的其他情况。现在我们来看看处理这种需求的 JavaScript 中可用的循环结构。
前提: | 基本的电脑知识,对 HTML 与 CSS 有基本的了解,及已阅读: JavaScript first steps(JS 的入门). |
---|---|
目标: | 学习如何在 JS 里面使用循环语句。 |
来一起循环
循环,循环,循环。就与这些:popular breakfast cereals, roller coasters and musical production 一样,类似存在于编程中。编程中的循环也是一直重复着去做一件事 - 此处循环便是编程中的术语。
让我们来想一下下图,这位农夫考虑为他的家庭做一周的食物计划,他或许就需要执行一段循环:
一段循环通常需要一个或多个条件:
- **一个开始条件,**它被初始化为一个特定的值 - 这是循环的起点 ("开始:我没有食物”,上面)。
- **一个结束条件,**这是循环停止的标准 - 通常计数器达到一定值。以上所说的“我有足够的食物”吗?假设他需要 10 份食物来养活他的家人。
- **一个迭代器,**这通常在每个连续循环上递增少量的计数器,直到达到退出条件。我们以前没有明确说明,但是我们可以考虑一下农民能够每小时收集 2 份食物。每小时后,他收集的食物量增加了两倍,他检查他是否有足够的食物。如果他已经达到 10 分(退出条件),他可以停止收集回家。
在 伪代码 中,这看起来就像下面这样:
loop(food = 0; foodNeeded = 10) {
if (food = foodNeeded) {
exit loop;
// 我们有足够的食物了,回家吧。
} else {
food += 2; // 花一个小时多收集两个食物。
// 循环将会继续执行。
}
}
所以需要的食物量定为 10,农民目前的数量为 0。在循环的每次迭代中,我们检查农民的食物量是否等于他需要的量。如果是这样,我们可以退出循环。如果没有,农民花一个小时收集两部分食物,循环再次运行。
为何?
在这一点上,你可能会了解循环中的高级概念,但你可能会认为“好的,但是,这有助于我编写更好的 JavaScript 代码?”正如我们前面所说,循环与所做的事情都是一样的,这对于快速完成重复任务是非常有用的。
通常,循环的每个连续迭代的代码将略有不同,这意味着你可以完成相同但略有不同的任务的全部负载 - 如果你有很多不同的计算要做,做不同的一个,不一样的一个又一个!
让我们来看一个例子来完美地说明为什么循环是一件好事。假设我们想在<canvas>
元素上绘制 100 个随机圆(按更新按钮一次又一次地运行示例以查看不同的随机集):
你现在不需要理解所有代码,但我们来看看实际绘制 100 个圆的那部分代码:
for (var i = 0; i < 100; i++) {
ctx.beginPath();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI);
ctx.fill();
}
random()
,在前面的代码中定义过了,返回一个0
到 x-1 间的整数。WIDTH
和HEIGHT
浏览器内部窗口的宽度和高度。
你应该有一个基本的想法 - 我们使用一个循环来运行这个代码的 100 次迭代,其中每一个在页面上的随机位置绘制一个圆。无论我们绘制 100 个圆,1000 还是 10,000,所需的代码量将是相同的。只有一个数字必须改变。
如果我们在这里没有使用循环,我们必须为我们想要绘制的每个圆重复以下代码:
ctx.beginPath();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI);
ctx.fill();
这将非常无聊而且很难维持高速。循环真的相当好用。
循环的标准
我们开始探索一些特定的循环结构。第一个,你会经常使用到它,for 循环——以下为 for 循环的语法:
for (initializer; condition; final-expression) {
// 要运行的代码
}
我们有:
-
关键字
for
,后跟一些括号。 -
在括号内,我们有三个项目,以分号分隔:
- 一个初始化器 - 这通常是一个设置为一个数字的变量,它被递增来计算循环运行的次数。它也有时被称为计数变量。
- 一个退出条件 - 如前面提到的,这个定义循环何时停止循环。这通常是一个表现为比较运算符的表达式,用于查看退出条件是否已满足的测试。
- 一个最终条件 - 这总是被判断(或运行),每个循环已经通过一个完整的迭代消失时间。它通常用于增加(或在某些情况下递减)计数器变量,使其更接近退出条件值。
-
一些包含代码块的花括号 - 每次循环迭代时都会运行这个代码。
我们来看一个真实的例子,所以我们可以看出这些做得更清楚。
var cats = ["Bill", "Jeff", "Pete", "Biggles", "Jasmin"];
var info = "My cats are called ";
var para = document.querySelector("p");
for (var i = 0; i < cats.length; i++) {
info += cats[i] + ", ";
}
para.textContent = info;
这给我们以下输出:
备注: 你可以在 GitHub 上找到这段示例代码。 (也可以在线运行)。
这显示了一个循环用于迭代数组中的项目,并与每个项目进行一些操作 - JavaScript 中非常常见的模式。这里:
-
迭代器
i
从 0 开始(var i = 0
)。 -
循环将会一直运行直到它不再小于猫数组的长度。这很重要 - 退出条件显示循环仍然运行的条件。所以在这种情况下,
< cats.length
仍然是真的,循环仍然运行。 -
在循环中,我们将当前的循环项(
cats[i]
是cats[当前下标的任何东西]
)以及逗号和空格连接到info
变量的末尾。所以:- 在第一次运行中,
i = 0
,所以cats[0] +', '
将被连接到info("Bill")
上。 - 在第二次运行中,
i = 1
,所以cats[1] +', '
将被连接到info("Jeff")
上。 - 等等。每次循环运行后,1 将被添加到 i(
i++
),然后进程将再次启动。
- 在第一次运行中,
-
当等于
cats.length
时,循环将停止,浏览器将移动到循环下面的下一个代码位。
备注:
我们将退出条件设为 < cats.length
,而不是 <= cats.length
是因为计算机从 0 开始,而不是 1 - 开始时 i
是 0,并且逐步增加到 i = 4
(最后一个数组的索引)。 cats.length
返回 5,因为数组中有 5 个项目,但是我们不希望达到 i = 5
,因为这将返回未定义的最后一个项目(没有索引为 5 的数组项目)。所以我们想要比 cats.length
少 1(i <
),而不是 cats.length
(i <=
)。
备注:
退出条件的一个常见错误是使它们使用“等于”(===
) 而不是说“小于或等于”(<=
)。如果我们想要运行我的循环到i = 5
,退出条件将需要是i <= cats.length
。如果我们设置为i === cats.length
,循环将不会运行,因为在第一次循环迭代时 i 不等于 5,所以循环会立即停止。
我们留下的一个小问题是最后的输出句子形式不是很好:
My cats are called Bill, Jeff, Pete, Biggles, Jasmin,
理想情况下,我们想改变最后循环迭代中的连接,以便在句子末尾没有逗号。嗯,没问题 - 我们可以很高兴地在我们的 for 循环中插入一个条件来处理这种特殊情况:
for (var i = 0; i < cats.length; i++) {
if (i === cats.length - 1) {
info += "and " + cats[i] + ".";
} else {
info += cats[i] + ", ";
}
}
备注: 你可以在 GitHub 上找到这个例子。(也可以在线运行)
警告:
使用for
- 与所有循环一样,你必须确保初始化程序被迭代,以便最终达到退出条件。如果没有,循环将永不停止,浏览器将强制它停止,否则会崩溃。这被称为无限循环。
使用 break 退出循环
如果要在所有迭代完成之前退出循环,可以使用 break 语句。当我们查看 switch 语句时,我们已经在上一篇文章中遇到过这样的情况 - 当 switch 语句中符合输入表达式的情况满足时,break 语句立即退出 switch 语句并移动到代码之后。
与循环相同 - break 语句将立即退出循环,并使浏览器移动到跟随它的任何代码。
说我们想搜索一系列联系人和电话号码,只返回我们想要找的号码?首先,一些简单的 HTML - 一个文本<input>
,允许我们输入一个名称来搜索,一个<button>
元素来提交搜索,以及一个<p>
元素显示结果:
<label for="search">Search by contact name: </label>
<input id="search" type="text" />
<button>Search</button>
<p></p>
然后是 JavaScript:
const contacts = [
"Chris:2232322",
"Sarah:3453456",
"Bill:7654322",
"Mary:9998769",
"Dianne:9384975",
];
const para = document.querySelector("p");
const input = document.querySelector("input");
const btn = document.querySelector("button");
btn.addEventListener("click", function () {
let searchName = input.value.toLowerCase();
input.value = "";
input.focus();
for (let i = 0; i < contacts.length; i++) {
let splitContact = contacts[i].split(":");
if (splitContact[0].toLowerCase() === searchName) {
para.textContent =
splitContact[0] + "'s number is " + splitContact[1] + ".";
break;
} else if (i === contacts.length - 1) {
para.textContent = "Contact not found.";
}
}
});
-
首先我们有一些变量定义 - 我们有一个联系信息数组,每个项目是一个字符串,包含一个以冒号分隔的名称和电话号码。
-
接下来,我们将一个事件监听器附加到按钮(
btn
)上,这样当按下它时,运行一些代码来执行搜索并返回结果。 -
我们将输入的值输入到一个名为
searchName
的变量中,然后清空文本输入并重新对准它,准备进行下一个搜索。 -
现在有趣的部分,for 循环:
- 我们的计数器开始时为在 0,直到计数器不再小于
contacts.length
,并在循环的每次迭代之后将i
递增 1。 - 在循环中,我们首先将当前联系人(
contacts [i]
)拆分为冒号字符,并将生成的两个值存储在名为splitContact
的数组中。 - 然后,我们使用条件语句来测试
splitContact [0]
(联系人姓名)是否等于输入的searchName
。如果是,我们在段落中输入一个字符串来报告联系人的号码,并使用 break 来结束循环。
- 我们的计数器开始时为在 0,直到计数器不再小于
-
在
(contacts.length-1)
迭代后,如果联系人姓名与输入的搜索不符,则段落文本设置为“未找到联系人”,循环继续迭代。
备注: 你可以在 GitHub 上找到这个例子或在线运行。
使用 continue 跳过迭代
continue 语句以类似的方式工作,而不是完全跳出循环,而是跳过当前循环而执行下一个循环。我们来看另外一个例子,它把一个数字作为一个输入,并且只返回开平方之后为整数的数字(整数)。
HTML 与最后一个例子基本相同 - 一个简单的文本输入和一个输出段落。JavaScript 也是一样的,虽然循环本身有点不同:
var num = input.value;
for (var i = 1; i <= num; i++) {
var sqRoot = Math.sqrt(i);
if (Math.floor(sqRoot) !== sqRoot) {
continue;
}
para.textContent += i + " ";
}
Here's the output:
- 在这种情况下,输入应为数字(
num
)。for 循环给定一个从 1 开始的计数器(在这种情况下,我们对 0 不感兴趣),一个退出条件,当计数器大于输入num
时循环将停止,并且迭代器的计数器将每次增加 1。 - 在循环中,我们使用
Math.sqrt(i)
找到每个数字的平方根,然后测试平方根是否是一个整数,通过判断当它被向下取整时,它是否与自身相同(这是Math.floor(...)
对传递的数字的作用)。 - 如果平方根和四舍五入的平方根不相等(
!==
),则表示平方根不是整数,因此我们对此不感兴趣。在这种情况下,我们使用 continue 语句跳过当前循环而执行下一个循环迭代,而不在任何地方记录该数字。 - 如果平方根是一个整数,我们完全跳过 if 块,所以 continue 语句不被执行; 相反,我们将当前 i 值加上一个空格连接到段落内容的末尾。
备注: 你可以在 GitHub 上查看完整代码,或者在线运行。
while 语句和 do...while 语句
for
不是 JavaScript 中唯一可用的循环类型。实际上还有很多其他的,而现在你不需要理解所有这些,所以值得看几个人的结构,这样你就可以在稍微不同的方式识别出相同的功能。
首先,我们来看看 while 循环。这个循环的语法如下所示:
initializer
while (condition) {
// 要运行的代码
final-expression
}
除了在循环之前设置初始化器变量,并且在运行代码之后,循环中包含 final-expression,而不是这两个项目被包含在括号中,这与以前的 for 循环非常类似。退出条件包含在括号内,前面是 while 关键字而不是 for。
同样的三个项目仍然存在,它们仍然以与 for 循环中相同的顺序定义 - 这是有道理的,因为你必须先定义一个初始化器,然后才能检查它是否已到达退出条件; 在循环中的代码运行(迭代已经完成)之后,运行最后的条件,这只有在尚未达到退出条件时才会发生。
我们再来看看我们的猫列表示例,但是重写了一个 while 循环:
var i = 0;
while (i < cats.length) {
if (i === cats.length - 1) {
info += "and " + cats[i] + ".";
} else {
info += cats[i] + ", ";
}
i++;
}
备注: This still works just the same as expected — have a look at it running live on GitHub (also view the full source code).
do...while循环非常类似但在 while 后提供了终止条件:
initializer
do {
// 要运行的代码
final-expression
} while (condition)
在这种情况下,在循环开始之前,初始化程序先重新开始。do 关键字直接在包含要运行的代码的花括号和终止条件之前。
这里的区别在于退出条件是一切都包含在括号中,而后面是一个 while 关键字。在 do ... while 循环中,花括号中的代码总是在检查之前运行一次,以查看是否应该再次执行(在 while 和 for 中,检查首先出现,因此代码可能永远不会执行)。
我们再次重写我们的猫列表示例,以使用 do...while 循环:
var i = 0;
do {
if (i === cats.length - 1) {
info += "and " + cats[i] + ".";
} else {
info += cats[i] + ", ";
}
i++;
} while (i < cats.length);
备注: 再一次,它正如我们期望的那样工作 — 看一看它在Github 在线运行 (或者查看完整源代码).
警告: 使用 while 和 do...while — 所有循环都一样 — 你必须保证初始变量是迭代的,那么它才会逐渐地达到退出条件。不然,它将会永远循环下去,要么浏览器会强制终止它,要么它自己会崩溃。这称为无限循环。
主动学习:启动倒计时!
在这个练习中,我们希望你打印出一个简单的启动倒计时到输出框,从 10 到关闭。具体来说,我们希望你:
-
从 10 下降到 0.我们为你提供了一个初始化器 - var i = 10;
-
对于每次迭代,创建一个新的段落并将其附加到输出<div>,我们使用
var output = document.querySelector('.output');
。在评论中,我们为你提供了需要在循环中某处使用的三条代码行:var para = document.createElement('p');
—新建一个段落。output.appendChild(para);
— 将段落附加到输出<div>
中。para.textContent =
— 段落内的文字等于你放在右侧的任何内容。
-
不同的迭代数字需要将不同的文本放在该迭代的段落中(你需要一个条件语句和多个
para.textContent = lines
):- 如果数字是 10,打印“Countdown 10”到段落。
- 如果数字为 0,请打印“Blast off!”到段落。
- 对于任何其他数字,只打印段落的数字。
-
记住要包括一个迭代器!然而,在这个例子中,我们在每次迭代之后都下降,而不是上升,所以你不想要
i++
- 你如何向下迭代?
如果你犯了错误,你可以随时使用“重置”按钮重置该示例。如果你真的卡住了,请按“显示解决方案”来查看解决方案。
主动学习:填写来宾列表
在本练习中,我们希望你获取存储在数组中的名称列表,并将其放入来宾列表中。但这不是那么容易 - 我们不想让菲尔和洛拉进来,因为他们是贪婪和粗鲁的,总是吃所有的食物!我们有两个名单,一个是客人承认的,一个是客人拒绝的。
具体来说,我们希望你:
-
编写一个循环,它将从 0 迭代到 people 数组的长度。你需要从一个初始化器
var i = 0
开始,但是你需要什么退出条件? -
在每个循环迭代期间,使用条件语句检查当前数组项是否等于“Phil”或“Lola”:
- 如果是,则将数组项连接到拒绝段落的
textContent
的末尾,后跟逗号和空格。 - 如果不是,则将数组项连接到接收段落的
textContent
的末尾,后跟逗号和空格。
- 如果是,则将数组项连接到拒绝段落的
我们已经提供给你:
var i = 0;
— 你的初始化程序refused.textContent +=
- 将连接某些东西的行的开头,结束于refused.textContent
。admitted.textContent +=
- 将连接某些内容到一行的结尾的行的开始。
额外的奖金问题 - 成功完成上述任务后,你将留下两个名称列表,用逗号分隔,但它们将不整齐 - 每个结尾处都会有一个逗号。你可以制定出如何在每种情况下编写最后一个逗号的行,并添加一个完整的停止?看看有用的字符串方法文章的帮助。
如果你犯了错误,你可以随时使用“重置”按钮重置该示例。如果你真的卡住了,请按“显示解决方案”来查看解决方案。
应该使用哪种循环类型?
对于基本用途,for,while 和 do ... while 循环大部分可互换。他们都可以用来解决相同的问题,你使用哪一个将在很大程度上取决于你的个人偏好 - 哪一个你最容易记住或最直观的。我们再来看看他们。
首先是 for
:
for (initializer; exit-condition; final-expression) { // code to run }
while
:
initializer while (exit-condition) { // 要运行的代码 final-expression }
最后是 do...while
:
initializer do { // 要运行的代码 final-expression } while (exit-condition)
我们建议使用for
,因为它可能是最简单地帮你记住一切 - 初始化程序,退出条件和最终表达式都必须整齐地放入括号,所以很容易看到他们在哪里并检查你没有丢失他们。
备注: 还有其他循环类型/特性,这些特性在 高级/专门 的情况下是有用的,超出了本文的范围。如果你想进一步了解循环学习,请阅读我们的高级循环和迭代指南。
技能测试!
你已经到达了本文的末尾,但你还记得最重要的知识吗?在你继续学习之前,你可以做一些深入的测试,来验证你已经记住了这些知识——请参阅技能测试:循环。
结论
本文向你展示了背后的基本概念,以及 JavaScript 中循环代码时可用的不同选项。你现在应该明白为什么循环是一个处理重复代码的好机制,并且在你自己的例子中使用它们!
如果你有什么不明白的地方,可以再通读一遍,或者联系我们寻求帮助。
参见
- Loops and iteration in detail
- for statement reference
- while and do...while references
- break and continue references
- What's the Best Way to Write a JavaScript For Loop? — some advanced loop best practices