循环和迭代
循环提供了一种快速简便的重复执行操作的方式。本章是 JavaScript 指南 的一部分,介绍了 JavaScript 中可用的不同迭代语句。
你可以将循环看作是游戏的电脑版,在游戏中你告诉别人朝一个方向走 X 步,然后朝另一个方向走 Y 步。例如,“向东走五步”这个想法可以用循环这样表示:
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 语句如下所示:
for (initialization; condition; afterthought)
statement
当一个 for 循环执行时,会发生以下情况:
- 初始化表达式
initialization(如果有)会被执行。这个表达式通常会初始化一个或多个循环计数器,但语法允许任何复杂度的表达式。这个表达式也可以声明变量。 condition表达式会被求值。如果condition的值为 true,循环语句就会执行。否则,for循环终止。(如果完全省略condition表达式,则条件被假定为 true。)statement会被执行。要执行多个语句,请使用块语句 ({ }) 将这些语句组合起来。- 如果存在,更新表达式
afterthought会被执行。 - 控制流返回到第 2 步。
示例
在下面的例子中,函数包含一个 for 语句,用于计算滚动列表(一个允许多选的 <select> 元素)中已选选项的数量。
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。
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 语句如下所示:
do
statement
while (condition);
在检查条件之前,statement 总是会至少执行一次。(要执行多个语句,请使用块语句 ({ }) 将这些语句组合起来。)
如果 condition 为 true,语句会再次执行。每次执行结束时,都会检查条件。当条件为 false 时,执行停止,控制权传递给 do...while 之后的语句。
示例
在下面的例子中,do 循环至少迭代一次,并重复迭代直到 i 不再小于 5。
let i = 0;
do {
i += 1;
console.log(i);
} while (i < 5);
while 语句
只要指定的条件计算结果为 true,while 语句就会执行其语句。一个 while 语句如下所示:
while (condition)
statement
如果 condition 变为 false,循环内的 statement 将停止执行,控制权将传递给循环之后的语句。
条件测试发生在循环中 statement 执行之前。如果条件返回 true,statement 会被执行,然后再次测试 condition。如果条件返回 false,执行停止,控制权传递给 while 之后的语句。
要执行多个语句,请使用块语句 ({ }) 将这些语句组合起来。
示例 1
下面的 while 循环只要 n 小于 3 就会一直迭代:
let n = 0;
let x = 0;
while (n < 3) {
n++;
x += n;
}
每次迭代,循环都会将 n 递增并将该值加到 x 上。因此,x 和 n 的取值如下:
- 第一次循环后:
n=1,x=1 - 第二次循环后:
n=2,x=3 - 第三次循环后:
n=3,x=6
完成第三次循环后,条件 n < 3 不再为 true,所以循环终止。
示例 2
避免无限循环。确保循环中的条件最终会变为 false——否则,循环将永远不会终止!下面 while 循环中的语句将永远执行,因为条件永远不会变为 false:
// Infinite loops are bad!
while (true) {
console.log("Hello, world!");
}
label 语句
label 为语句提供了一个标识符,以便你可以在程序的其他地方引用它。例如,你可以使用标签来标识一个循环,然后使用 break 或 continue 语句来指示程序应该中断循环还是继续执行。
标签语句的语法如下:
label:
statement
label 的值可以是任何不是保留字的 JavaScript 标识符。你用标签标识的 statement 可以是任何语句。有关使用标签语句的示例,请参阅下面 break 和 continue 的例子。
break 语句
使用 break 语句来终止循环、switch 语句,或与标签语句结合使用。
- 当你不带标签使用
break时,它会立即终止最内层的while、do-while、for或switch语句,并将控制权转移到其后的语句。 - 当你带标签使用
break时,它会终止指定的标签语句。
break 语句的语法如下:
break;
break label;
- 第一种语法形式终止最内层的循环或
switch语句。 - 第二种语法形式终止指定的带标签的语句。
示例 1
下面的例子遍历数组中的元素,直到找到值为 theValue 的元素的索引:
for (let i = 0; i < a.length; i++) {
if (a[i] === theValue) {
break;
}
}
示例 2:跳转到标签
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 语句可以用来重新开始一个 while、do-while、for 或 label 语句。
- 当你不带标签使用
continue时,它会终止最内层while、do-while或for语句的当前迭代,并以该循环的下一次迭代继续执行。与break语句不同,continue不会完全终止循环的执行。在while循环中,它会跳回到条件判断。在for循环中,它会跳到increment-expression。 - 当你带标签使用
continue时,它会作用于由该标签标识的循环语句。
continue 语句的语法如下:
continue;
continue label;
示例 1
下面的例子展示了一个带有 continue 语句的 while 循环,当 i 的值为 3 时执行。因此,n 的取值为 1、3、7 和 12。
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 语句的顶部继续执行。
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;
}
console.log(j, "is odd.");
}
console.log("i =", i);
console.log("j =", j);
}
for...in 语句
for...in 语句会用一个指定的变量来迭代一个对象的所有可枚举属性。对于每个不同的属性,JavaScript 都会执行指定的语句。一个 for...in 语句如下所示:
for (variable in object)
statement
示例
下面的函数接受一个对象及其名称作为参数。然后它会遍历该对象的所有属性,并返回一个列出属性名称及其值的字符串。
function dumpProps(obj, objName) {
let result = "";
for (const i in obj) {
result += `${objName}.${i} = ${obj[i]}<br>`;
}
result += "<hr>";
return result;
}
对于一个具有 make 和 model 属性的对象 car,result 将是:
car.make = Ford car.model = Mustang
数组
虽然将此用作遍历 Array 元素的方式可能很诱人,但 for...in 语句会返回用户定义的属性名称以及数字索引。
因此,在遍历数组时,最好使用带有数字索引的传统 for 循环,因为如果你修改了 Array 对象(例如添加自定义属性或方法),for...in 语句会遍历用户定义的属性以及数组元素。
for...of 语句
for...of 语句会创建一个循环,用于迭代可迭代对象(包括 Array、Map、Set、arguments 对象等),并为每个不同属性的值执行带有语句的自定义迭代钩子。
for (variable of iterable)
statement
下面的例子展示了 for...of 循环和 for...in 循环之间的区别。for...in 迭代属性名,而 for...of 迭代属性值:
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...of 和 for...in 语句也可以与解构一起使用。例如,你可以使用 Object.entries() 同时遍历对象的键和值。
const obj = { foo: 1, bar: 2 };
for (const [key, val] of Object.entries(obj)) {
console.log(key, val);
}
// "foo" 1
// "bar" 2