迴圈程式碼

程式語言對於快速完成重複性任務非常有用,從多個基本計算到幾乎任何其他需要完成大量類似工作的情況。在這裡,我們將瞭解 JavaScript 中可用於處理此類需求的迴圈結構。

先決條件 對 HTML、CSS 和 JavaScript 初步知識 的基本瞭解。
目標 瞭解如何在 JavaScript 中使用迴圈。

為什麼迴圈有用?

迴圈就是一遍又一遍地做同樣的事情。通常,程式碼每次迴圈都會略有不同,或者執行相同的程式碼但使用不同的變數。

迴圈程式碼示例

假設我們想在 <canvas> 元素上繪製 100 個隨機圓圈(按“更新”按鈕反覆執行示例以檢視不同的隨機集合)

以下是實現此示例的 JavaScript 程式碼

js
const btn = document.querySelector("button");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

document.addEventListener("DOMContentLoaded", () => {
  canvas.width = document.documentElement.clientWidth;
  canvas.height = document.documentElement.clientHeight;
});

function random(number) {
  return Math.floor(Math.random() * number);
}

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  for (let i = 0; i < 100; i++) {
    ctx.beginPath();
    ctx.fillStyle = "rgb(255 0 0 / 50%)";
    ctx.arc(
      random(canvas.width),
      random(canvas.height),
      random(50),
      0,
      2 * Math.PI,
    );
    ctx.fill();
  }
}

btn.addEventListener("click", draw);

使用和不使用迴圈

現在您不必理解所有程式碼,但讓我們看一下實際繪製 100 個圓圈的程式碼部分

js
for (let i = 0; i < 100; i++) {
  ctx.beginPath();
  ctx.fillStyle = "rgb(255 0 0 / 50%)";
  ctx.arc(
    random(canvas.width),
    random(canvas.height),
    random(50),
    0,
    2 * Math.PI,
  );
  ctx.fill();
}
  • random(x)(在程式碼的前面定義)返回 0x-1 之間的一個整數。

您應該掌握基本思路——我們使用迴圈執行此程式碼的 100 次迭代,每次迭代都在頁面上的隨機位置繪製一個圓圈。無論我們繪製 100 個、1000 個還是 10000 個圓圈,所需的程式碼量都相同。只需要更改一個數字。

如果我們不在這裡使用迴圈,則必須為我們要繪製的每個圓圈重複以下程式碼

js
ctx.beginPath();
ctx.fillStyle = "rgb(255 0 0 / 50%)";
ctx.arc(
  random(canvas.width),
  random(canvas.height),
  random(50),
  0,
  2 * Math.PI,
);
ctx.fill();

這會變得非常無聊且難以維護。

遍歷集合

大多數時候,當您使用迴圈時,您會有一組專案,並希望對每個專案執行某些操作。

一種集合型別是 Array,我們在本課程的 陣列 章節中遇到過。但 JavaScript 中還有其他集合,包括 SetMap

for...of 迴圈

迴圈遍歷集合的基本工具是 for...of 迴圈

js
const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"];

for (const cat of cats) {
  console.log(cat);
}

在此示例中,for (const cat of cats) 表示

  1. 給定集合 cats,獲取集合中的第一個專案。
  2. 將其分配給變數 cat,然後執行花括號 {} 之間的程式碼。
  3. 獲取下一個專案,並重復 (2),直到到達集合的末尾。

map() 和 filter()

JavaScript 還為集合提供了更專門的迴圈,我們將在本文中介紹其中兩個。

您可以使用 map() 對集合中的每個專案執行某些操作,並建立一個包含已更改專案的新集合

js
function toUpper(string) {
  return string.toUpperCase();
}

const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"];

const upperCats = cats.map(toUpper);

console.log(upperCats);
// [ "LEOPARD", "SERVAL", "JAGUAR", "TIGER", "CARACAL", "LION" ]

在這裡,我們將一個函式傳遞給 cats.map()map() 為陣列中的每個專案呼叫該函式一次,並將該專案作為引數傳遞。然後,它將每次函式呼叫的返回值新增到一個新陣列中,最後返回該新陣列。在本例中,我們提供的函式將專案轉換為大寫,因此結果陣列包含我們所有的大寫貓

js
[ "LEOPARD", "SERVAL", "JAGUAR", "TIGER", "CARACAL", "LION" ]

您可以使用 filter() 測試集合中的每個專案,並建立一個僅包含匹配專案的新集合

js
function lCat(cat) {
  return cat.startsWith("L");
}

const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"];

const filtered = cats.filter(lCat);

console.log(filtered);
// [ "Leopard", "Lion" ]

這看起來很像 map(),除了我們傳入的函式返回一個 布林值:如果它返回 true,則該專案將包含在新陣列中。我們的函式測試該專案是否以字母“L”開頭,因此結果是一個僅包含名稱以“L”開頭的貓的陣列

js
[ "Leopard", "Lion" ]

請注意,map()filter() 通常都與函式表示式一起使用,我們將在 函式 模組中學習。使用函式表示式,我們可以將上面的示例重寫得更加緊湊

js
const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"];

const filtered = cats.filter((cat) => cat.startsWith("L"));
console.log(filtered);
// [ "Leopard", "Lion" ]

標準 for 迴圈

在上面的“繪製圓圈”示例中,您沒有要迴圈遍歷的專案集合:您實際上只想執行 100 次相同的程式碼。在這種情況下,您應該使用 for 迴圈。它具有以下語法

js
for (initializer; condition; final-expression) {
  // code to run
}

在這裡,我們有

  1. 關鍵字 for,後跟一些括號。
  2. 在括號內,我們有三個專案,用分號分隔
    1. 初始化器——這通常是一個設定為數字的變數,它會遞增以計算迴圈執行的次數。它有時也稱為計數器變數
    2. 條件——這定義了迴圈何時停止迴圈。這通常是一個包含比較運算子的表示式,用於測試是否已滿足退出條件。
    3. 最終表示式——每次迴圈完成一次完整迭代時都會對其進行評估(或執行)。它通常用於遞增(或在某些情況下遞減)計數器變數,以使其更接近條件不再為 true 的點。
  3. 一些包含程式碼塊的花括號——此程式碼將在每次迴圈迭代時執行。

計算平方

讓我們來看一個真實的例子,以便我們可以更清楚地瞭解它們的作用。

js
const results = document.querySelector("#results");

function calculate() {
  for (let i = 1; i < 10; i++) {
    const newResult = `${i} x ${i} = ${i * i}`;
    results.textContent += `${newResult}\n`;
  }
  results.textContent += "\nFinished!\n\n";
}

const calculateBtn = document.querySelector("#calculate");
const clearBtn = document.querySelector("#clear");

calculateBtn.addEventListener("click", calculate);
clearBtn.addEventListener("click", () => (results.textContent = ""));

這將給我們以下輸出

此程式碼計算 1 到 9 的數字的平方,並輸出結果。程式碼的核心是執行計算的 for 迴圈。

讓我們將 for (let i = 1; i < 10; i++) 行分解成三個部分

  1. let i = 1:計數器變數 i1 開始。請注意,我們必須對計數器使用 let,因為我們每次迴圈都會重新分配它。
  2. i < 10:只要 i 小於 10,就繼續迴圈。
  3. i++:每次迴圈都將 i 加 1。

在迴圈內部,我們計算 i 的當前值的平方,即:i * i。我們建立一個表示我們執行的計算和結果的字串,並將此字串新增到輸出文字中。我們還添加了 \n,因此我們新增的下一個字串將從新行開始。所以

  1. 在第一次執行中,i = 1,因此我們將新增 1 x 1 = 1
  2. 在第二次執行中,i = 2,因此我們將新增 2 x 2 = 4
  3. 依此類推…
  4. i 變成等於 10 時,我們將停止執行迴圈並直接移動到迴圈下方的下一段程式碼,在新行上打印出 Finished! 訊息。

使用 for 迴圈迴圈遍歷集合

您可以使用 for 迴圈來迭代集合,而不是 for...of 迴圈。

讓我們再次看看上面的 for...of 示例

js
const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"];

for (const cat of cats) {
  console.log(cat);
}

我們可以像這樣重寫該程式碼

js
const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"];

for (let i = 0; i < cats.length; i++) {
  console.log(cats[i]);
}

在此迴圈中,我們從 0 開始 i,並在 i 達到陣列長度時停止。然後在迴圈內部,我們使用 i 依次訪問陣列中的每個專案。

這工作得很好,並且在早期版本的 JavaScript 中,for...of 不存在,因此這是遍歷陣列的標準方法。但是,它提供了更多在程式碼中引入錯誤的機會。例如

  • 您可能會將 i1 開始,忘記第一個陣列索引是零,而不是 1。
  • 您可能會在 i <= cats.length 處停止,忘記最後一個數組索引位於 length - 1 處。

由於這些原因,如果可以,最好使用 for...of

有時您仍然需要使用 for 迴圈來迭代陣列。例如,在下面的程式碼中,我們希望記錄一條列出我們貓的訊息

js
const cats = ["Pete", "Biggles", "Jasmine"];

let myFavoriteCats = "My cats are called ";

for (const cat of cats) {
  myFavoriteCats += `${cat}, `;
}

console.log(myFavoriteCats); // "My cats are called Pete, Biggles, Jasmine, "

最終的輸出句子格式不太好

My cats are called Pete, Biggles, Jasmine,

我們希望它能夠以不同的方式處理最後一隻貓,如下所示

My cats are called Pete, Biggles, and Jasmine.

但要做到這一點,我們需要知道我們在最後一次迴圈迭代中,為此我們可以使用 for 迴圈並檢查 i 的值

js
const cats = ["Pete", "Biggles", "Jasmine"];

let myFavoriteCats = "My cats are called ";

for (let i = 0; i < cats.length; i++) {
  if (i === cats.length - 1) {
    // We are at the end of the array
    myFavoriteCats += `and ${cats[i]}.`;
  } else {
    myFavoriteCats += `${cats[i]}, `;
  }
}

console.log(myFavoriteCats); // "My cats are called Pete, Biggles, and Jasmine."

使用 break 退出迴圈

如果要在完成所有迭代之前退出迴圈,可以使用 break 語句。我們之前在檢視 switch 語句 時已經遇到過這種情況——當 switch 語句中滿足與輸入表示式匹配的 case 時,break 語句會立即退出 switch 語句並繼續執行其後的程式碼。

迴圈也是如此——break 語句將立即退出迴圈並使瀏覽器繼續執行其後的任何程式碼。

假設我們要搜尋聯絡人及其電話號碼的陣列並僅返回我們想要查詢的號碼?首先,一些簡單的 HTML——一個文字 <input> 允許我們輸入要搜尋的姓名,一個 <button> 元素提交搜尋,以及一個 <p> 元素在其中顯示結果

html
<label for="search">Search by contact name: </label>
<input id="search" type="text" />
<button>Search</button>

<p></p>

現在開始 JavaScript

js
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", () => {
  const searchName = input.value.toLowerCase();
  input.value = "";
  input.focus();
  para.textContent = "";
  for (const contact of contacts) {
    const splitContact = contact.split(":");
    if (splitContact[0].toLowerCase() === searchName) {
      para.textContent = `${splitContact[0]}'s number is ${splitContact[1]}.`;
      break;
    }
  }
  if (para.textContent === "") {
    para.textContent = "Contact not found.";
  }
});
  1. 首先,我們有一些變數定義——我們有一個聯絡人資訊的陣列,每個專案都是一個包含姓名和電話號碼(用冒號分隔)的字串。
  2. 接下來,我們將事件偵聽器附加到按鈕 (btn) 上,以便按下它時執行一些程式碼以執行搜尋並返回結果。
  3. 我們將輸入到文字輸入中的值儲存在一個名為 searchName 的變數中,然後清空文字輸入並再次聚焦它,準備進行下一次搜尋。請注意,我們還在字串上運行了 toLowerCase() 方法,以便搜尋不區分大小寫。
  4. 現在進入有趣的部分,for...of 迴圈
    1. 在迴圈內部,我們首先在冒號字元處分割當前聯絡人,並將結果兩個值儲存在一個名為 splitContact 的陣列中。
    2. 然後,我們使用條件語句來測試 splitContact[0](聯絡人的姓名,再次使用 toLowerCase() 小寫)是否等於輸入的 searchName。如果是,我們將一個字串輸入段落以報告聯絡人的號碼是什麼,並使用 break 結束迴圈。
  5. 迴圈結束後,我們檢查是否設定了聯絡人,如果沒有,我們將段落文字設定為“未找到聯絡人”。

注意:您也可以在 GitHub 上檢視完整的原始碼(還可以 檢視其執行情況)。

使用 continue 跳過迭代

continue 語句的工作方式類似於 break,但它不是完全跳出迴圈,而是跳到迴圈的下一輪迭代。讓我們看另一個例子,它接收一個數字作為輸入,並只返回整數(整數)的平方數。

HTML 與上一個例子基本相同——一個簡單的數字輸入和一個用於輸出的段落。

html
<label for="number">Enter number: </label>
<input id="number" type="number" />
<button>Generate integer squares</button>

<p>Output:</p>

JavaScript 也大多相同,儘管迴圈本身略有不同。

js
const para = document.querySelector("p");
const input = document.querySelector("input");
const btn = document.querySelector("button");

btn.addEventListener("click", () => {
  para.textContent = "Output: ";
  const num = input.value;
  input.value = "";
  input.focus();
  for (let i = 1; i <= num; i++) {
    let sqRoot = Math.sqrt(i);
    if (Math.floor(sqRoot) !== sqRoot) {
      continue;
    }
    para.textContent += `${i} `;
  }
});

以下是輸出結果。

  1. 在這種情況下,輸入應該是一個數字(num)。for 迴圈從 1 開始計數(因為我們對 0 不感興趣),退出條件是當計數器大於輸入 num 時停止迴圈,以及每次迭代將計數器加 1 的迭代器。
  2. 在迴圈內部,我們使用 Math.sqrt(i) 找到每個數字的平方根,然後透過測試其舍入到最接近的整數後的值是否與其自身相同來檢查平方根是否為整數(這就是 Math.floor() 對傳遞給它的數字所做的操作)。
  3. 如果平方根和舍入後的平方根不相等(!==),則表示平方根不是整數,因此我們對此不感興趣。在這種情況下,我們使用 continue 語句跳到下一輪迴圈迭代,而無需在任何地方記錄數字。
  4. 如果平方根是整數,我們將完全跳過 if 程式碼塊,因此不會執行 continue 語句;相反,我們在段落內容的末尾連線當前 i 值加上一個空格。

注意:您也可以在 GitHub 上檢視完整原始碼(還可以 檢視其執行效果)。

while 和 do...while

for 不是 JavaScript 中唯一可用的迴圈型別。實際上還有很多其他的迴圈型別,雖然您現在不需要理解所有這些,但值得看看其他一些迴圈的結構,以便您能夠以稍微不同的方式識別相同的功能。

首先,讓我們看一下 while 迴圈。此迴圈的語法如下所示。

js
initializer
while (condition) {
  // code to run

  final-expression
}

它的工作方式與 for 迴圈非常相似,只是初始化變數在迴圈之前設定,而最終表示式包含在迴圈內部程式碼之後,而不是將這兩個專案包含在括號內。條件包含在括號內,括號前是 while 關鍵字而不是 for

這三個專案仍然存在,並且它們的定義順序與 for 迴圈中相同。這是因為您必須在檢查條件是否為真之前定義初始化器。然後在迴圈內部的程式碼執行後(一個迭代已完成)執行最終表示式,這隻有在條件仍然為真的情況下才會發生。

讓我們再看一下我們的貓咪列表示例,但重寫為使用 while 迴圈。

js
const cats = ["Pete", "Biggles", "Jasmine"];

let myFavoriteCats = "My cats are called ";

let i = 0;

while (i < cats.length) {
  if (i === cats.length - 1) {
    myFavoriteCats += `and ${cats[i]}.`;
  } else {
    myFavoriteCats += `${cats[i]}, `;
  }

  i++;
}

console.log(myFavoriteCats); // "My cats are called Pete, Biggles, and Jasmine."

注意:這仍然按預期工作——檢視它 在 GitHub 上的即時執行效果(還可以檢視 完整原始碼)。

do...while 迴圈非常相似,但提供了 while 結構的一種變體。

js
initializer
do {
  // code to run

  final-expression
} while (condition)

在這種情況下,初始化器再次出現在迴圈開始之前。關鍵字直接位於包含要執行的程式碼和最終表示式的花括號之前。

do...while 迴圈和 while 迴圈之間的主要區別在於,do...while 迴圈內的程式碼至少會執行一次。這是因為條件位於迴圈內部程式碼之後。因此,我們始終執行該程式碼,然後檢查是否需要再次執行它。在 whilefor 迴圈中,檢查首先發生,因此程式碼可能永遠不會執行。

讓我們再次重寫我們的貓咪列表示例以使用 do...while 迴圈。

js
const cats = ["Pete", "Biggles", "Jasmine"];

let myFavoriteCats = "My cats are called ";

let i = 0;

do {
  if (i === cats.length - 1) {
    myFavoriteCats += `and ${cats[i]}.`;
  } else {
    myFavoriteCats += `${cats[i]}, `;
  }

  i++;
} while (i < cats.length);

console.log(myFavoriteCats); // "My cats are called Pete, Biggles, and Jasmine."

注意:同樣,這仍然按預期工作——檢視它 在 GitHub 上的即時執行效果(還可以檢視 完整原始碼)。

警告:對於 whiledo...while——以及所有迴圈——您必須確保初始化器遞增或根據情況遞減,以便條件最終變為假。否則,迴圈將永遠持續下去,瀏覽器要麼強制其停止,要麼會崩潰。這稱為無限迴圈

主動學習:啟動倒計時

在本練習中,我們希望您將一個簡單的發射倒計時列印到輸出框中,從 10 倒數到發射。具體來說,我們希望您:

  • 從 10 倒數到 0。我們為您提供了一個初始化器——let i = 10;
  • 對於每次迭代,建立一個新段落並將其附加到輸出 <div>,我們已使用 const output = document.querySelector('.output'); 選擇了它。在註釋中,我們為您提供了三行程式碼,需要在迴圈中的某個地方使用它們。
    • const para = document.createElement('p');——建立一個新段落。
    • output.appendChild(para);——將段落附加到輸出 <div>
    • para.textContent =——使段落內的文字等於等號右側您放置的任何內容。
  • 不同的迭代次數需要在該迭代的段落中放置不同的文字(您需要一個條件語句和多行 para.textContent =)。
    • 如果數字是 10,則將“倒計時 10”列印到段落中。
    • 如果數字是 0,則將“發射!”列印到段落中。
    • 對於任何其他數字,只需將數字列印到段落中。
  • 請記住包含迭代器!但是,在本例中,我們在每次迭代後遞減計數,而不是遞增,因此您不需要 i++——您如何向下迭代?

注意:如果您開始鍵入迴圈(例如 while(i>=0)),瀏覽器可能會卡住,因為您尚未輸入結束條件。因此,請注意這一點。您可以先在註釋中編寫程式碼以解決此問題,並在完成後刪除註釋。

如果您犯了錯誤,您可以隨時使用“重置”按鈕重置示例。如果您真的卡住了,請按“顯示解決方案”以檢視解決方案。

css
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}

主動學習:填寫賓客名單

在本練習中,我們希望您獲取儲存在陣列中的姓名列表,並將它們放入訪客列表中。但這並不容易——我們不想讓 Phil 和 Lola 進來,因為他們貪婪且粗魯,總是吃掉所有食物!我們有兩個列表,一個用於允許的訪客,一個用於拒絕的訪客。

具體來說,我們希望您:

  • 編寫一個迴圈,遍歷 people 陣列。
  • 在每次迴圈迭代期間,使用條件語句檢查當前陣列項是否等於“Phil”或“Lola”。
    • 如果是,則將陣列項連線到 refused 段落的 textContent 末尾,後跟逗號和空格。
    • 如果不是,則將陣列項連線到 admitted 段落的 textContent 末尾,後跟逗號和空格。

我們已經為您提供了:

  • refused.textContent +=——將某些內容連線到 refused.textContent 末尾的行開頭。
  • admitted.textContent +=——將某些內容連線到 admitted.textContent 末尾的行開頭。

額外獎勵問題——在成功完成上述任務後,您將得到兩個以逗號分隔的姓名列表,但它們將不整齊——每個列表末尾都將有一個逗號。您可以想出如何編寫在每種情況下都切掉最後一個逗號並在末尾新增句點的程式碼行嗎?檢視 有用的字串方法 文章以獲取幫助。

如果您犯了錯誤,您可以隨時使用“重置”按鈕重置示例。如果您真的卡住了,請按“顯示解決方案”以檢視解決方案。

應該使用哪種迴圈型別?

如果您正在遍歷一個數組或其他支援它的物件,並且不需要訪問每個專案的索引位置,那麼 for...of 是最佳選擇。它更易於閱讀,出錯的可能性也更小。

對於其他用途,forwhiledo...while 迴圈基本上是可以互換的。它們都可以用來解決相同的問題,您使用哪一個在很大程度上取決於您的個人喜好——您覺得哪一個最容易記住或最直觀。我們建議至少一開始使用 for,因為它可能是最容易記住所有內容的——初始化器、條件和最終表示式都必須整齊地放入括號中,因此很容易看到它們在哪裡並檢查您是否沒有遺漏它們。

讓我們再看一遍它們。

首先是 for...of

js
for (const item of array) {
  // code to run
}

for:

js
for (initializer; condition; final-expression) {
  // code to run
}

while:

js
initializer
while (condition) {
  // code to run

  final-expression
}

最後是 do...while

js
initializer
do {
  // code to run

  final-expression
} while (condition)

注意:還有其他迴圈型別/功能,它們在高階/特殊情況下非常有用,並且超出了本文的範圍。如果您想進一步學習迴圈,請閱讀我們高階的 迴圈和迭代指南

測試你的技能!

您已閱讀完本文,但您還記得最重要的資訊嗎?在繼續之前,您可以找到一些進一步的測試來驗證您是否保留了這些資訊——請參閱 測試您的技能:迴圈

結論

本文向您揭示了 JavaScript 中迴圈程式碼背後的基本概念以及可用的不同選項。您現在應該清楚為什麼迴圈是處理重複程式碼的良好機制,並且渴望在您自己的示例中使用它們!

如果您有任何不理解的地方,請隨時重新閱讀本文,或 聯絡我們 尋求幫助。

另請參閱