迴圈和迭代
迴圈提供了一種快速簡便的重複執行操作的方式。本章是 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