循环 是一种重复运行同一代码的方法。

“while” 循环

while 循环的语法如下:

  1. while (condition) {
  2. // 代码
  3. // 所谓的“循环体”
  4. }

当 condition 为真时,执行循环体的 code。
例如,以下将循环输出当 i < 3 时的 i 值:

  1. let i = 0;
  2. while (i < 3) { // 依次显示 0、1 和 2
  3. alert( i );
  4. i++;
  5. }

循环体的单次执行叫作 一次迭代。上面示例中的循环进行了三次迭代。

如果上述示例中没有 i++,那么循环(理论上)会永远重复执行下去。实际上,浏览器提供了阻止这种循环的方法,我们可以通过终止进程,来停掉服务器端的 JavaScript。

任何表达式或变量都可以是循环条件,而不仅仅是比较。在 while 中的循环条件会被计算,计算结果会被转化为布尔值。
例如,while (i != 0) 可简写为 while (i):

  1. let i = 3;
  2. while (i) { // 当 i 变成 0 时,条件为假,循环终止
  3. alert( i );
  4. i--;
  5. }

单行循环体不需要大括号
如果循环体只有一条语句,则可以省略大括号 {…}:

  1. let i = 3;
  2. while (i) alert(i--);

“do…while” 循环

使用 do..while 语法可以将条件检查移至循环体 下面

  1. do {
  2. // 循环体
  3. } while (condition);

循环首先执行循环体,然后检查条件,当条件为真时,重复执行循环体。
例如:

  1. let i = 0;
  2. do {
  3. alert( i );
  4. i++;
  5. } while (i < 3);

这种形式的语法很少使用,除非你希望不管条件是否为真,循环体 至少执行一次。通常我们更倾向于使用另一个形式:while(…) {…}。

“for” 循环

for 循环更加复杂,但它是最常使用的循环形式。
for 循环看起来就像这样:

  1. for (begin; condition; step) {
  2. // ……循环体……
  3. }

下述循环从 i 等于 0 到 3(但不包括 3)运行 alert(i):

  1. for (let i = 0; i < 3; i++) { // 结果为 0、1、2
  2. alert(i);
  3. }

我们逐个部分分析 for 循环:

语句段
begin i = 0 进入循环时执行一次。
condition i < 3 在每次循环迭代之前检查,如果为 false,停止循环。
body(循环体) alert(i) 条件为真时,重复运行。
step i++ 在每次循环体迭代后执行。

一般循环算法的工作原理如下:

  1. 开始运行
  2. (如果 condition 成立 运行 body 然后运行 step)
  3. (如果 condition 成立 运行 body 然后运行 step)
  4. (如果 condition 成立 运行 body 然后运行 step)
  5. ...

所以,begin 执行一次,然后进行迭代:每次检查 condition 后,执行 body 和 step。

内联变量声明
这里“计数”变量 i 是在循环中声明的。这叫做“内联”变量声明。这样的变量只在循环中可见。

  1. for (let i = 0; i < 3; i++) {
  2. alert(i); // 0, 1, 2
  3. }
  4. alert(i); // 错误,没有这个变量。

除了定义一个变量,我们也可以使用现有的变量:

  1. let i = 0;
  2. for (i = 0; i < 3; i++) { // 使用现有的变量
  3. alert(i); // 0, 1, 2
  4. }
  5. alert(i); //3,可见,因为是在循环之外声明的

省略语句段

for 循环的任何语句段都可以被省略。
例如,如果我们在循环开始时不需要做任何事,我们就可以省略 begin 语句段。
就像这样:

  1. let i = 0; // 我们已经声明了 i 并对它进行了赋值
  2. for (; i < 3; i++) { // 不再需要 "begin" 语句段
  3. alert( i ); // 0, 1, 2
  4. }

我们也可以移除 step 语句段:

  1. let i = 0;
  2. for (; i < 3;) {
  3. alert( i++ );
  4. }

该循环与 while (i < 3) 等价。
实际上我们可以删除所有内容,从而创建一个无限循环:

  1. for (;;) {
  2. // 无限循环
  3. }

请注意 for 的两个 ; 必须存在,否则会出现语法错误。

跳出循环

通常条件为假时,循环会终止。
但我们随时都可以使用 break 指令强制退出。
例如,下面这个循环要求用户输入一系列数字,在输入的内容不是数字时“终止”循环。

  1. let sum = 0;
  2. while (true) {
  3. let value = +prompt("Enter a number", '');
  4. if (!value) break; // (*)
  5. sum += value;
  6. }
  7. alert( 'Sum: ' + sum );

如果用户输入空行或取消输入,在 (*) 行的 break 指令会被激活。它立刻终止循环,将控制权传递给循环后的第一行,即,alert。

根据需要,”无限循环 + break” 的组合非常适用于不必在循环开始/结束时检查条件,但需要在中间甚至是主体的多个位置进行条件检查的情况。

继续下一次迭代

continue 指令是 break 的“轻量版”。它不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环(如果条件允许的话)。

如果我们完成了当前的迭代,并且希望继续执行下一次迭代,我们就可以使用它。
下面这个循环使用 continue 来只输出奇数:

  1. for (let i = 0; i < 10; i++) {
  2. //如果为真,跳过循环体的剩余部分。
  3. if (i % 2 == 0) continue;
  4. alert(i); // 1,然后 3,5,7,9
  5. }

continue 指令利于减少嵌套
显示奇数的循环可以像下面这样:

  1. for (let i = 0; i < 10; i++) {
  2. if (i % 2) {
  3. alert( i );
  4. }
  5. }

从技术角度看,它与上一个示例完全相同。当然,我们可以将代码包装在 if 块而不使用 continue。
但在副作用方面,它多创建了一层嵌套(大括号内的 alert 调用)。如果 if 中代码有多行,则可能会降低代码整体的可读性。

禁止 break/continue 在 ‘?’ 的右边
请注意非表达式的语法结构不能与三元运算符 ? 一起使用。特别是 break/continue 这样的指令是不允许这样使用的。

例如,我们使用如下代码:

  1. if (i > 5) {
  2. alert(i);
  3. } else {
  4. continue;
  5. }

……用问号重写:

  1. (i > 5) ? alert(i) : continue; // continue 不允许在这个位置

……代码会停止运行,并显示有语法错误。
这是不(建议)使用问号 ? 运算符替代 if 语句的另一个原因。

break/continue 标签

有时候我们需要从一次从多层嵌套的循环中跳出来。
例如,下述代码中我们的循环使用了 i 和 j,从 (0,0) 到 (3,3) 提示坐标 (i, j):

  1. for (let i = 0; i < 3; i++) {
  2. for (let j = 0; j < 3; j++) {
  3. let input = prompt(`Value at coords (${i},${j})`, '');
  4. // 如果我想从这里退出并直接执行 alert('Done!')
  5. }
  6. }
  7. alert('Done!');

在 input 之后的普通 break 只会打破内部循环。这还不够 —— 标签可以实现这一功能!
标签 是在循环之前带有冒号的标识符:

  1. labelName: for (...) {
  2. ...
  3. }

break 语句跳出循环至标签处:

  1. outer: for (let i = 0; i < 3; i++) {
  2. for (let j = 0; j < 3; j++) {
  3. let input = prompt(`Value at coords (${i},${j})`, '');
  4. // 如果是空字符串或被取消,则中断并跳出这两个循环。
  5. if (!input) break outer; // (*)
  6. // 用得到的值做些事……
  7. }
  8. }
  9. alert('Done!');

上述代码中,break outer 向上寻找名为 outer 的标签并跳出当前循环。
因此,控制权直接从 (*) 转至 alert(‘Done!’)。
我们还可以将标签移至单独一行:

  1. outer:
  2. for (let i = 0; i < 3; i++) { ... }

continue 指令也可以与标签一起使用。在这种情况下,执行跳转到标记循环的下一次迭代。

标签并不允许“跳到”所有位置
标签不允许我们跳到代码的任意位置。
例如,这样做是不可能的

  1. break label; // 无法跳转到这个标签
  2. label: for (...)

只有在循环内部才能调用 break/continue,并且标签必须位于指令上方的某个位置

总结

我们学习了三种循环:

  • while —— 每次迭代之前都要检查条件。
  • do..while —— 每次迭代后都要检查条件。
  • for (;;) —— 每次迭代之前都要检查条件,可以使用其他设置。

通常使用 while(true) 来构造“无限”循环。这样的循环和其他循环一样,都可以通过 break 指令来终止。
如果我们不想在当前迭代中做任何事,并且想要转移至下一次迭代,那么可以使用 continue 指令。
break/continue 支持循环前的标签。标签是 break/continue 跳出嵌套循环以转到外部的唯一方法。