循环和迭代

循环提供了一种快速简便的方法来重复执行某些操作。本节介绍 JavaScript 指南中可用的不同迭代语句。

您可以将循环视为计算机化的游戏版本,您告诉某人向一个方向走 *X* 步,然后向另一个方向走 *Y* 步。例如,想法“向东走五步”可以用循环这样表达

js
for (let step = 0; step < 5; step++) {
  // Runs 5 times, with values of step 0 through 4.
  console.log("Walking east one step");
}

循环有很多种,但它们基本上都做同样的事情:重复执行某个操作若干次。(注意,这个数字可能是零!)

各种循环机制提供了不同的方法来确定循环的起点和终点。有各种情况更容易通过一种类型的循环而不是其他类型来处理。

JavaScript 提供的循环语句是

for 语句

一个 for 循环会一直重复执行,直到指定条件计算结果为 false。JavaScript for 循环类似于 Java 和 C for 循环。

for 语句的格式如下

js
for (initialization; condition; afterthought)
  statement

for 循环执行时,会发生以下情况

  1. 执行初始化表达式 initialization(如果有)。此表达式通常初始化一个或多个循环计数器,但语法允许任意复杂度的表达式。此表达式还可以声明变量。
  2. 计算 condition 表达式。如果 condition 的值为 true,则执行循环语句。否则,for 循环终止。(如果完全省略 condition 表达式,则假定条件为 true。)
  3. 执行 statement。要执行多个语句,请使用 块语句 ({ }) 对这些语句进行分组。
  4. 如果存在,则执行更新表达式 afterthought
  5. 控制返回步骤 2。

示例

在下面的示例中,函数包含一个 for 语句,该语句计算滚动列表中所选选项的数量(允许进行多项选择的 <select> 元素)。

HTML

html
<form name="selectForm">
  <label for="musicTypes"
    >Choose some music types, then click the button below:</label
  >
  <select id="musicTypes" name="musicTypes" multiple>
    <option selected>R&B</option>
    <option>Jazz</option>
    <option>Blues</option>
    <option>New Age</option>
    <option>Classical</option>
    <option>Opera</option>
  </select>
  <button id="btn" type="button">How many are selected?</button>
</form>

JavaScript

这里,for 语句声明变量 i 并将其初始化为 0。它检查 i 是否小于 <select> 元素中的选项数量,执行随后的 if 语句,并在每次通过循环后将 i 增加 1。

js
function countSelected(selectObject) {
  let numberSelected = 0;
  for (let i = 0; i < selectObject.options.length; i++) {
    if (selectObject.options[i].selected) {
      numberSelected++;
    }
  }
  return numberSelected;
}

const btn = document.getElementById("btn");

btn.addEventListener("click", () => {
  const musicTypes = document.selectForm.musicTypes;
  console.log(`You have selected ${countSelected(musicTypes)} option(s).`);
});

do...while 语句

do...while 语句会重复执行,直到指定条件计算结果为 false。

do...while 语句的格式如下

js
do
  statement
while (condition);

statement 始终在检查条件之前执行一次。(要执行多个语句,请使用块语句 ({ }) 对这些语句进行分组。)

如果 conditiontrue,则再次执行语句。在每次执行结束时,都会检查条件。当条件为 false 时,执行停止,控制权传递到 do...while 之后的语句。

示例

在以下示例中,do 循环至少迭代一次,并重复迭代,直到 i 不再小于 5

js
let i = 0;
do {
  i += 1;
  console.log(i);
} while (i < 5);

while 语句

while 语句会执行其语句,只要指定条件计算结果为 truewhile 语句的格式如下

js
while (condition)
  statement

如果 condition 变成 false,则循环内的 statement 停止执行,控制权传递到循环之后的语句。

条件测试发生在循环中执行 statement *之前*。如果条件返回 true,则执行 statement 并再次测试 condition。如果条件返回 false,则执行停止,控制权传递到 while 之后的语句。

要执行多个语句,请使用块语句 ({ }) 对这些语句进行分组。

示例 1

以下 while 循环会一直迭代,只要 n 小于 3

js
let n = 0;
let x = 0;
while (n < 3) {
  n++;
  x += n;
}

每次迭代时,循环都会增加 n 并将该值添加到 x。因此,xn 会获得以下值

  • 第一次通过后:n = 1x = 1
  • 第二次通过后:n = 2x = 3
  • 第三次通过后:n = 3x = 6

完成第三次通过后,条件 n < 3 不再为 true,因此循环终止。

示例 2

避免无限循环。确保循环中的条件最终变成 false——否则,循环将永远不会终止!以下 while 循环中的语句会永远执行,因为条件永远不会变成 false

js
// Infinite loops are bad!
while (true) {
  console.log("Hello, world!");
}

带标签的语句

label 为语句提供一个标识符,让您可以在程序中的其他位置引用该标识符。例如,您可以使用标签来标识循环,然后使用 breakcontinue 语句来指示程序是否应该中断循环或继续执行。

带标签语句的语法如下

js
label:
  statement

label 的值可以是任何 JavaScript 标识符,但不能是保留字。您使用标签标识的 statement 可以是任何语句。有关使用带标签语句的示例,请参阅下面的 breakcontinue 示例。

break 语句

使用 break 语句来终止循环、switch 或与带标签语句一起使用。

  • 当您在没有标签的情况下使用 break 时,它会立即终止最内层的封闭 whiledo-whileforswitch,并将控制权转移到接下来的语句。
  • 当您在有标签的情况下使用 break 时,它会终止指定的带标签语句。

break 语句的语法如下

js
break;
break label;
  1. 语法的第一种形式会终止最内层的封闭循环或 switch
  2. 语法的第二种形式会终止指定的封闭带标签语句。

示例 1

以下示例迭代数组中的元素,直到找到值为 theValue 的元素的索引

js
for (let i = 0; i < a.length; i++) {
  if (a[i] === theValue) {
    break;
  }
}

示例 2:跳到标签

js
let x = 0;
let z = 0;
labelCancelLoops: while (true) {
  console.log("Outer loops:", x);
  x += 1;
  z = 1;
  while (true) {
    console.log("Inner loops:", z);
    z += 1;
    if (z === 10 && x === 10) {
      break labelCancelLoops;
    } else if (z === 10) {
      break;
    }
  }
}

continue 语句

continue 语句可以用来重新启动 whiledo-whileforlabel 语句。

  • 当您在没有标签的情况下使用 continue 时,它会终止最内层的封闭 whiledo-whilefor 语句的当前迭代,并继续执行循环的下一个迭代。与 break 语句不同,continue 不会完全终止循环的执行。在 while 循环中,它会跳回到条件。在 for 循环中,它会跳到 increment-expression
  • 当您在有标签的情况下使用 continue 时,它适用于使用该标签标识的循环语句。

continue 语句的语法如下

js
continue;
continue label;

示例 1

以下示例显示了一个带有 continue 语句的 while 循环,该语句在 i 的值为 3 时执行。因此,n 的值为 13712

js
let i = 0;
let n = 0;
while (i < 5) {
  i++;
  if (i === 3) {
    continue;
  }
  n += i;
  console.log(n);
}
// Logs:
// 1 3 7 12

如果您注释掉 continue;,循环将运行到最后,您将看到 1,3,6,10,15

示例 2

一个名为checkiandj的语句包含一个名为checkj的语句。如果遇到continue,程序将终止checkj的当前迭代并开始下一个迭代。每次遇到continue时,checkj都会重复迭代,直到其条件返回false。当返回false时,checkiandj语句的剩余部分将完成,并且checkiandj会重复迭代,直到其条件返回false。当返回false时,程序将在checkiandj之后的语句处继续执行。

如果continue有一个名为checkiandj的标签,程序将在checkiandj语句的顶部继续执行。

js
let i = 0;
let j = 10;
checkiandj: while (i < 4) {
  console.log(i);
  i += 1;
  checkj: while (j > 4) {
    console.log(j);
    j -= 1;
    if (j % 2 === 0) {
      continue checkj;
    }
    console.log(j, "is odd.");
  }
  console.log("i =", i);
  console.log("j =", j);
}

for...in 语句

for...in 语句会将指定的变量迭代到对象的全部可枚举属性。对于每个不同的属性,JavaScript都会执行指定的语句。for...in 语句的格式如下所示:

js
for (variable in object)
  statement

示例

下面的函数以一个对象和对象的名称作为参数。然后,它会遍历对象的所有属性,并返回一个字符串,其中列出了属性名称及其值。

js
function dumpProps(obj, objName) {
  let result = "";
  for (const i in obj) {
    result += `${objName}.${i} = ${obj[i]}<br>`;
  }
  result += "<hr>";
  return result;
}

对于一个具有makemodel属性的car对象,result将是:

car.make = Ford
car.model = Mustang

数组

虽然可能会想要用它来遍历Array元素,但for...in语句会返回用户定义属性的名称以及数值索引。

因此,在遍历数组时,最好使用传统的for循环以及数值索引,因为for...in语句会遍历用户定义的属性,以及数组元素,如果您修改了Array对象(例如,添加自定义属性或方法)。

for...of 语句

for...of 语句会创建一个循环,它会遍历可迭代对象(包括ArrayMapSetarguments对象等),并使用要为每个不同属性的值执行的语句调用自定义迭代钩子。

js
for (variable of iterable)
  statement

下面的示例展示了for...of循环和for...in循环之间的区别。for...in循环遍历属性名,而for...of循环遍历属性值。

js
const arr = [3, 5, 7];
arr.foo = "hello";

for (const i in arr) {
  console.log(i);
}
// "0" "1" "2" "foo"

for (const i of arr) {
  console.log(i);
}
// Logs: 3 5 7

for...offor...in语句也可以和解构赋值一起使用。例如,您可以使用Object.entries()同时遍历对象的键和值。

js
const obj = { foo: 1, bar: 2 };

for (const [key, val] of Object.entries(obj)) {
  console.log(key, val);
}
// "foo" 1
// "bar" 2