循环代码

编程语言对于快速完成重复性任务非常有用,从多个基本计算到几乎任何其他需要完成大量类似工作项的情况。在这里,我们将了解 JavaScript 中可用于处理此类需求的循环结构。

¥Programming languages are very useful for rapidly completing repetitive tasks, from multiple basic calculations to just about any other situation where you've got a lot of similar items of work to complete. Here we'll look at the loop structures available in JavaScript that handle such needs.

先决条件: 对 HTML、CSS 和 JavaScript 第一步 有基本了解。
目标: 了解如何在 JavaScript 中使用循环。

为什么循环有用?

¥Why are loops useful?

循环就是一遍又一遍地做同样的事情。通常,每次循环时代码都会略有不同,或者运行相同的代码但使用不同的变量。

¥Loops are all about doing the same thing over and over again. Often, the code will be slightly different each time round the loop, or the same code will run but with different variables.

循环代码示例

¥Looping code example

假设我们想在 <canvas> 元素上绘制 100 个随机圆圈(按“更新”按钮一次又一次运行示例以查看不同的随机集):

¥Suppose we wanted to draw 100 random circles on a <canvas> element (press the Update button to run the example again and again to see different random sets):

html
<button>Update</button> <canvas></canvas>
css
html {
  width: 100%;
  height: inherit;
  background: #ddd;
}

canvas {
  display: block;
}

body {
  margin: 0;
}

button {
  position: absolute;
  top: 5px;
  left: 5px;
}

以下是实现此示例的 JavaScript 代码:

¥Here's the JavaScript code that implements this example:

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);

有环和无环

¥With and without a loop

你暂时不必理解所有代码,但让我们看一下实际绘制 100 个圆圈的代码部分:

¥You don't have to understand all the code for now, but let's look at the part of the code that actually draws the 100 circles:

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 个还是 10,000 个圆圈,所需的代码量都是相同的。只需要更改一个数字。

¥You should get the basic idea — we are using a loop to run 100 iterations of this code, each one of which draws a circle in a random position on the page. The amount of code needed would be the same whether we were drawing 100 circles, 1000, or 10,000. Only one number has to change.

如果我们在这里不使用循环,则必须为要绘制的每个圆圈重复以下代码:

¥If we weren't using a loop here, we'd have to repeat the following code for every circle we wanted to draw:

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();

这会变得非常无聊并且难以维护。

¥This would get very boring and difficult to maintain.

循环遍历集合

¥Looping through a collection

大多数时候,当你使用循环时,你将拥有一个项目集合,并且希望对每个项目执行某些操作。

¥Most of the time when you use a loop, you will have a collection of items and want to do something with every item.

一种类型的集合是 Array,我们在本课程的 数组 章中遇到过它。但 JavaScript 中还有其他集合,包括 SetMap

¥One type of collection is the Array, which we met in the Arrays chapter of this course. But there are other collections in JavaScript as well, including Set and Map.

for...of 循环

¥The for...of loop

循环集合的基本工具是 for...of 循环:

¥The basic tool for looping through a collection is the for...of loop:

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

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

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

¥In this example, for (const cat of cats) says:

  1. 给定集合 cats,获取集合中的第一项。
  2. 将其分配给变量 cat,然后运行大括号 {} 之间的代码。
  3. 获取下一个项目,然后重复 (2),直到到达集合的末尾。

map() and filter()

JavaScript 还有更专门的集合循环,我们将在这里提到其中两个。

¥JavaScript also has more specialized loops for collections, and we'll mention two of them here.

你可以使用 map() 对集合中的每个项目执行某些操作,并创建一个包含更改的项目的新集合:

¥You can use map() to do something to each item in a collection and create a new collection containing the changed items:

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() 对数组中的每个项目调用一次该函数,并传入该项目。然后它将每个函数调用的返回值添加到一个新数组中,最后返回新数组。在这种情况下,我们提供的函数将项目转换为大写,因此结果数组包含所有大写的猫:

¥Here we pass a function into cats.map(), and map() calls the function once for each item in the array, passing in the item. It then adds the return value from each function call to a new array, and finally returns the new array. In this case the function we provide converts the item to uppercase, so the resulting array contains all our cats in uppercase:

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

你可以使用 filter() 测试集合中的每个项目,并创建一个仅包含匹配项目的新集合:

¥You can use filter() to test each item in a collection, and create a new collection containing only items that match:

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(),除了我们传入的函数返回 boolean:如果返回 true,则该项目包含在新数组中。我们的函数测试该项目是否以字母 "L" 开头,因此结果是一个仅包含名字以 "L" 开头的猫的数组:

¥This looks a lot like map(), except the function we pass in returns a boolean: if it returns true, then the item is included in the new array. Our function tests that the item starts with the letter "L", so the result is an array containing only cats whose names start with "L":

js
[ "Leopard", "Lion" ]

请注意,map()filter() 都经常与函数表达式一起使用,我们将在 函数 模块中学习。使用函数表达式,我们可以将上面的示例重写得更加紧凑:

¥Note that map() and filter() are both often used with function expressions, which we will learn about in the Functions module. Using function expressions we could rewrite the example above to be much more compact:

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

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

for 循环的标准

¥The standard for loop

在上面的 "画圆圈" 示例中,你没有要循环的项目集合:你真的只想运行相同的代码 100 次。在这种情况下,你应该使用 for 循环。它具有以下语法:

¥In the "drawing circles" example above, you don't have a collection of items to loop through: you really just want to run the same code 100 times. In a case like that, you should use the for loop. This has the following syntax:

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

这里我们有:

¥Here we have:

  1. 关键字 for,后跟一些括号。
  2. 括号内有三项,用分号分隔:
    1. 初始化器 - 这通常是一个设置为数字的变量,该数字会递增以计算循环运行的次数。有时它也被称为计数器变量。
    2. 条件 - 定义循环何时停止循环。这通常是一个带有比较运算符的表达式,用于测试是否满足退出条件。
    3. 最终表达式 - 每次循环完成一次完整迭代时,它总是被评估(或运行)。它通常用于递增(或在某些情况下递减)计数器变量,使其更接近条件不再为 true 的点。
  3. 一些包含代码块的大括号 - 每次循环迭代时都会运行该代码。

计算平方

¥Calculating squares

让我们看一个真实的例子,这样我们就可以更清楚地想象它们的作用。

¥Let's look at a real example so we can visualize what these do more clearly.

html
<button id="calculate">Calculate</button>
<button id="clear">Clear</button>
<pre id="results"></pre>
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 = ""));

这给了我们以下输出:

¥This gives us the following output:

此代码计算从 1 到 9 的数字的平方,并写出结果。代码的核心是执行计算的 for 循环。

¥This code calculates squares for the numbers from 1 to 9, and writes out the result. The core of the code is the for loop that performs the calculation.

让我们将 for (let i = 1; i < 10; i++) 系列分为三部分:

¥Let's break down the for (let i = 1; i < 10; i++) line into its three pieces:

  1. let i = 1:计数器变量 i1 开始。请注意,我们必须使用 let 作为计数器,因为每次循环时我们都会重新分配它。
  2. i < 10:只要 i 小于 10,就继续循环。
  3. i++:每次循环时将 i 加 1。

在循环内部,我们计算 i 当前值的平方,即:i * i。我们创建一个字符串来表示我们所做的计算和结果,并将该字符串添加到输出文本中。我们还添加 \n,因此我们添加的下一个字符串将在新行开始。所以:

¥Inside the loop, we calculate the square of the current value of i, that is: i * i. We create a string expressing the calculation we made and the result, and add this string to the output text. We also add \n, so the next string we add will begin on a new line. So:

  1. 在第一次运行期间,i = 1,所以我们将添加 1 x 1 = 1
  2. 在第二次运行期间,i = 2,所以我们将添加 2 x 2 = 4
  3. 等等…
  4. i 等于 10 时,我们将停止运行循环并直接移至循环下方的下一段代码,在新行上打印 Finished! 消息。

使用 for 循环遍历集合

¥Looping through collections with a for loop

你可以使用 for 循环来迭代集合,而不是 for...of 循环。

¥You can use a for loop to iterate through a collection, instead of a for...of loop.

让我们再看一下上面的 for...of 示例:

¥Let's look again at our for...of example above:

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

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

我们可以像这样重写该代码:

¥We could rewrite that code like this:

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 依次访问数组中的每个项目。

¥In this loop we're starting i at 0, and stopping when i reaches the length of the array. Then inside the loop, we're using i to access each item in the array in turn.

这工作得很好,在 JavaScript 的早期版本中,for...of 不存在,所以这是迭代数组的标准方法。但是,它提供了更多将错误引入代码的机会。例如:

¥This works just fine, and in early versions of JavaScript, for...of didn't exist, so this was the standard way to iterate through an array. However, it offers more chances to introduce bugs into your code. For example:

  • 你可能会从 1 开始 i,忘记第一个数组索引是 0,而不是 1。
  • 你可能会停在 i <= cats.length 处,忘记最后一个数组索引位于 length - 1 处。

出于此类原因,如果可以的话,通常最好使用 for...of

¥For reasons like this, it's usually best to use for...of if you can.

有时你仍然需要使用 for 循环来迭代数组。例如,在下面的代码中,我们想要记录一条列出我们的猫的消息:

¥Sometimes you still need to use a for loop to iterate through an array. For example, in the code below we want to log a message listing our cats:

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, "

最终输出句子的格式不太好:

¥The final output sentence isn't very well-formed:

My cats are called Pete, Biggles, Jasmine,

我们希望它以不同的方式处理最后一只猫,如下所示:

¥We'd prefer it to handle the last cat differently, like this:

My cats are called Pete, Biggles, and Jasmine.

但要做到这一点,我们需要知道何时处于最终循环迭代,为此,我们可以使用 for 循环并检查 i 的值:

¥But to do this we need to know when we are on the final loop iteration, and to do that we can use a for loop and examine the value of 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 退出循环

¥Exiting loops with break

如果要在所有迭代完成之前退出循环,可以使用 break 语句。我们在上一篇文章中查看 switch 语句 时已经遇到过这一点 — 当 switch 语句中遇到与输入表达式匹配的情况时,break 语句立即退出 switch 语句并继续执行其后面的代码。

¥If you want to exit a loop before all the iterations have been completed, you can use the break statement. We already met this in the previous article when we looked at switch statements — when a case is met in a switch statement that matches the input expression, the break statement immediately exits the switch statement and moves on to the code after it.

这与循环相同 - break 语句将立即退出循环并使浏览器移至其后面的任何代码。

¥It's the same with loops — a break statement will immediately exit the loop and make the browser move on to any code that follows it.

假设我们想要搜索一系列联系人和调用号码并仅返回我们想要查找的号码?首先,一些简单的 HTML — 文本 <input> 允许我们输入要搜索的名称,<button> 元素用于提交搜索,<p> 元素用于显示结果:

¥Say we wanted to search through an array of contacts and telephone numbers and return just the number we wanted to find? First, some simple HTML — a text <input> allowing us to enter a name to search for, a <button> element to submit a search, and a <p> element to display the results in:

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

<p></p>

现在来看看 JavaScript:

¥Now on to the 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 上的完整源代码(也可以查看 看到它实时运行)。

¥Note: You can view the full source code on GitHub too (also see it running live).

使用 continue 跳过迭代

¥Skipping iterations with continue

continue 语句的工作方式与 break 类似,但它不是完全跳出循环,而是跳到循环的下一次迭代。让我们看另一个示例,它以数字作为输入,并仅返回整数(整数)的平方的数字。

¥The continue statement works similarly to break, but instead of breaking out of the loop entirely, it skips to the next iteration of the loop. Let's look at another example that takes a number as an input, and returns only the numbers that are squares of integers (whole numbers).

HTML 与上一个示例基本相同 - 一个简单的数字输入和一个用于输出的段落。

¥The HTML is basically the same as the last example — a simple numeric input, and a paragraph for output.

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

<p>Output:</p>

JavaScript 也基本相同,尽管循环本身有点不同:

¥The JavaScript is mostly the same too, although the loop itself is a bit different:

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} `;
  }
});

这是输出:

¥Here's the output:

  1. 在这种情况下,输入应该是数字 (num)。for 循环有一个从 1 开始的计数器(因为在本例中我们对 0 不感兴趣)、一个退出条件(表示当计数器变得大于输入 num 时循环将停止)以及一个将 1 添加到循环的迭代器。 每次计数器。
  2. 在循环内部,我们使用 Math.sqrt(i) 求出每个数字的平方根,然后通过测试平方根是否与自身相同来检查平方根是否为整数(这就是 Math.floor() 所做的) 它传递的数字)。
  3. 如果平方根和向下舍入的平方根不等于(!==),则意味着平方根不是整数,因此我们对此不感兴趣。在这种情况下,我们使用 continue 语句跳到下一个循环迭代,而不在任何地方记录数字。
  4. 如果平方根是整数,则完全跳过 if 块,因此不执行 continue 语句;相反,我们将当前 i 值与段落内容末尾的空格连接起来。

注意:你也可以查看 GitHub 上的完整源代码(也可以查看 看到它实时运行)。

¥Note: You can view the full source code on GitHub too (also see it running live).

同时并做...同时

¥while and do...while

for 并不是 JavaScript 中唯一可用的循环类型。实际上还有很多其他功能,虽然你现在不需要了解所有这些功能,但值得查看其他几个功能的结构,以便你可以以稍微不同的方式识别工作中的相同功能。

¥for is not the only type of loop available in JavaScript. There are actually many others and, while you don't need to understand all of these now, it is worth having a look at the structure of a couple of others so that you can recognize the same features at work in a slightly different way.

首先,让我们看一下 while 循环。该循环的语法如下所示:

¥First, let's have a look at the while loop. This loop's syntax looks like so:

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

  final-expression
}

这与 for 循环的工作方式非常相似,只不过初始化变量是在循环之前设置的,并且最终表达式包含在要运行的代码之后的循环内,而不是将这两项包含在括号内。条件包含在括号内,括号前面是 while 关键字而不是 for

¥This works in a very similar way to the for loop, except that the initializer variable is set before the loop, and the final-expression is included inside the loop after the code to run, rather than these two items being included inside the parentheses. The condition is included inside the parentheses, which are preceded by the while keyword rather than for.

相同的三个项目仍然存在,并且它们的定义顺序仍然与 for 循环中的顺序相同。这是因为你必须先定义初始化程序,然后才能检查条件是否为真。然后,在循环内的代码运行后(迭代已完成)运行最终表达式,只有当条件仍然为真时才会发生这种情况。

¥The same three items are still present, and they are still defined in the same order as they are in the for loop. This is because you must have an initializer defined before you can check whether or not the condition is true. The final-expression is then run after the code inside the loop has run (an iteration has been completed), which will only happen if the condition is still true.

让我们再次看看我们的猫列表示例,但重写为使用 while 循环:

¥Let's have a look again at our cats list example, but rewritten to use a while loop:

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 上实时运行(也查看 完整源代码)。

¥Note: This still works just the same as expected — have a look at it running live on GitHub (also view the full source code).

do...while 循环非常相似,但提供了 while 结构的变体:

¥The do...while loop is very similar, but provides a variation on the while structure:

js
initializer
do {
  // code to run

  final-expression
} while (condition)

在这种情况下,在循环开始之前,初始化器再次首先出现。关键字直接位于包含要运行的代码和最终表达式的大括号之前。

¥In this case, the initializer again comes first, before the loop starts. The keyword directly precedes the curly braces containing the code to run and the final expression.

do...while 循环和 while 循环之间的主要区别在于 do...while 循环内的代码始终至少执行一次。这是因为条件出现在循环内的代码之后。所以我们总是运行该代码,然后检查是否需要再次运行它。在 whilefor 循环中,首先进行检查,因此代码可能永远不会被执行。

¥The main difference between a do...while loop and a while loop is that the code inside a do...while loop is always executed at least once. That's because the condition comes after the code inside the loop. So we always run that code, then check to see if we need to run it again. In while and for loops, the check comes first, so the code might never be executed.

让我们再次重写 cat 列表示例以使用 do...while 循环:

¥Let's rewrite our cat listing example again to use a do...while loop:

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 上实时运行(也查看 完整源代码)。

¥Note: Again, this works just the same as expected — have a look at it running live on GitHub (also view the full source code).

警告:对于 while 和 do...while — 与所有循环一样 — 你必须确保初始化器递增,或者根据情况递减,因此条件最终变为 false。如果没有,循环将永远持续下去,浏览器要么强制它停止,要么崩溃。这称为无限循环。

¥Warning: With while and do...while — as with all loops — you must make sure that the initializer is incremented or, depending on the case, decremented, so the condition eventually becomes false. If not, the loop will go on forever, and either the browser will force it to stop, or it will crash. This is called an infinite loop.

主动学习:触发倒计时

¥Active learning: Launch countdown

在本练习中,我们希望你将一个简单的启动倒计时打印到输出框,从 10 到 Blastoff。具体来说,我们希望你:

¥In this exercise, we want you to print out a simple launch countdown to the output box, from 10 down to Blastoff. Specifically, we want you to:

  • 从 10 向下循环到 0。我们为你提供了一个初始化程序 - let i = 10;
  • 对于每次迭代,创建一个新段落并将其附加到我们使用 const output = document.querySelector('.output'); 选择的输出 <div>。在评论中,我们为你提供了需要在循环内某处使用的三个代码行:
    • const para = document.createElement('p'); — 创建一个新段落。
    • output.appendChild(para); — 将段落附加到输出 <div>
    • para.textContent = — 使段落内的文本等于你在等号之后放在右侧的任何内容。
  • 不同的迭代编号需要在该迭代的段落中放置不同的文本(你需要一个条件语句和多个 para.textContent = 行):
    • 如果数字是 10,则在该段落中打印 "倒计时 10"。
    • 如果数字为 0,则在该段落中打印 "触发!"。
    • 对于任何其他数字,只需打印该段落的数字。
  • 记住要包含一个迭代器!然而,在这个例子中,我们在每次迭代后倒数,而不是向上倒数,所以你不需要 i++ - 如何向下迭代?

注意:如果你开始输入循环(例如 (while(i>=0)),浏览器可能会卡住,因为你尚未输入结束条件。所以要小心这一点。你可以开始在注释中编写代码来处理此问题,并在完成后删除注释。

¥Note: If you start typing the loop (for example (while(i>=0)), the browser might get stuck because you have not yet entered the end condition. So be careful with this. You can start writing your code in a comment to deal with this issue and remove the comment after you finish.

如果你犯了错误,你可以随时使用 "重置" 按钮重置示例。如果你确实遇到困难,请按 "显示解决方案" 查看解决方案。

¥If you make a mistake, you can always reset the example with the "Reset" button. If you get really stuck, press "Show solution" to see a solution.

html
<h2>Live output</h2>
<div class="output" style="height: 410px;overflow: auto;"></div>

<h2>Editable code</h2>
<p class="a11y-label">
  Press Esc to move focus away from the code area (Tab inserts a tab character).
</p>
<textarea id="code" class="playable-code" style="height: 300px;width: 95%">
let output = document.querySelector('.output');
output.innerHTML = '';

// let i = 10;

// const para = document.createElement('p');
// para.textContent = ;
// output.appendChild(para);
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
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;
}
js
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "Show solution";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "Show solution") {
    textarea.value = solutionEntry;
    solution.value = "Hide solution";
  } else {
    textarea.value = userEntry;
    solution.value = "Show solution";
  }
  updateCode();
});

let jsSolution = `const output = document.querySelector('.output');
output.innerHTML = '';

let i = 10;

while (i >= 0) {
  const para = document.createElement('p');
  if (i === 10) {
    para.textContent = \`Countdown \${i}\`;
  } else if (i === 0) {
    para.textContent = 'Blast off!';
  } else {
    para.textContent = i;
  }

  output.appendChild(para);

  i--;
}`;

let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// stop tab key tabbing out of textarea and
// make it write a tab at the caret position instead

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  let caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );

  textarea.value = front + text + back;
  caretPos += text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// Update the saved userCode every time the user updates the text area code

textarea.onkeyup = () => {
  // We only want to save the state when the user code is being shown,
  // not the solution, so that solution is not saved over the user code
  if (solution.value === "Show solution") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};

主动学习:填写宾客名单

¥Active learning: Filling in a guest list

在本练习中,我们希望你获取存储在数组中的名称列表,并将它们放入宾客列表中。但这并不那么容易 - 我们不想让菲尔和劳拉进来,因为他们贪婪且粗鲁,而且总是吃掉所有的食物!我们有两份清单,一份供客人允许,一份供客人拒绝。

¥In this exercise, we want you to take a list of names stored in an array and put them into a guest list. But it's not quite that easy — we don't want to let Phil and Lola in because they are greedy and rude, and always eat all the food! We have two lists, one for guests to admit, and one for guests to refuse.

具体来说,我们希望你:

¥Specifically, we want you to:

  • 编写一个循环来遍历 people 数组。
  • 在每次循环迭代期间,使用条件语句检查当前数组项是否等于 "菲尔" 或 "罗拉":
    • 如果是,则将数组项连接到 refused 段落的 textContent 的末尾,后跟一个逗号和一个空格。
    • 如果不是,请将数组项连接到 admitted 段落的 textContent 的末尾,后跟一个逗号和一个空格。

我们已经为你提供了:

¥We've already provided you with:

  • refused.textContent += — 将在 refused.textContent 末尾连接某些内容的行的开头。
  • admitted.textContent += — 将在 admitted.textContent 末尾连接某些内容的行的开头。

额外的奖励问题 - 成功完成上述任务后,你会得到两个名字列表,用逗号分隔,但它们会很乱 - 每个列表的末尾都会有一个逗号。你能弄清楚如何编写在每种情况下将最后一个逗号切掉并在末尾添加句号的行吗?请参阅 有用的字符串方法 文章寻求帮助。

¥Extra bonus question — after completing the above tasks successfully, you will be left with two lists of names, separated by commas, but they will be untidy — there will be a comma at the end of each one. Can you work out how to write lines that slice the last comma off in each case, and add a full stop to the end? Have a look at the Useful string methods article for help.

如果你犯了错误,你可以随时使用 "重置" 按钮重置示例。如果你确实遇到困难,请按 "显示解决方案" 查看解决方案。

¥If you make a mistake, you can always reset the example with the "Reset" button. If you get really stuck, press "Show solution" to see a solution.

html
<h2>Live output</h2>
<div class="output" style="height: 100px;overflow: auto;">
  <p class="admitted">Admit:</p>
  <p class="refused">Refuse:</p>
</div>

<h2>Editable code</h2>
<p class="a11y-label">
  Press Esc to move focus away from the code area (Tab inserts a tab character).
</p>
<textarea id="code" class="playable-code" style="height: 400px;width: 95%">
const people = ['Chris', 'Anne', 'Colin', 'Terri', 'Phil', 'Lola', 'Sam', 'Kay', 'Bruce'];

const admitted = document.querySelector('.admitted');
const refused = document.querySelector('.refused');
admitted.textContent = 'Admit: ';
refused.textContent = 'Refuse: ';

// loop starts here

// refused.textContent += ;
// admitted.textContent += ;

</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="Reset" />
  <input id="solution" type="button" value="Show solution" />
</div>
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;
}
js
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "Show solution";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "Show solution") {
    textarea.value = solutionEntry;
    solution.value = "Hide solution";
  } else {
    textarea.value = userEntry;
    solution.value = "Show solution";
  }
  updateCode();
});

const jsSolution = `
const people = ['Chris', 'Anne', 'Colin', 'Terri', 'Phil', 'Lola', 'Sam', 'Kay', 'Bruce'];

const admitted = document.querySelector('.admitted');
const refused = document.querySelector('.refused');

admitted.textContent = 'Admit: ';
refused.textContent = 'Refuse: ';

for (const person of people) {
  if (person === 'Phil' || person === 'Lola') {
    refused.textContent += \`\${person}, \`;
  } else {
    admitted.textContent += \`\${person}, \`;
  }
}

refused.textContent = refused.textContent.slice(0,refused.textContent.length-2) + '.';
admitted.textContent = admitted.textContent.slice(0,admitted.textContent.length-2) + '.';`;

let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// stop tab key tabbing out of textarea and
// make it write a tab at the caret position instead

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  let caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );

  textarea.value = front + text + back;
  caretPos += text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// Update the saved userCode every time the user updates the text area code

textarea.onkeyup = () => {
  // We only want to save the state when the user code is being shown,
  // not the solution, so that solution is not saved over the user code
  if (solution.value === "Show solution") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};

你应该使用哪种循环类型?

¥Which loop type should you use?

如果你要迭代数组或其他支持它的对象,并且不需要访问每个项目的索引位置,那么 for...of 是最佳选择。它更容易阅读并且出错的可能性也更少。

¥If you're iterating through an array or some other object that supports it, and don't need access to the index position of each item, then for...of is the best choice. It's easier to read and there's less to go wrong.

对于其他用途,forwhiledo...while 循环在很大程度上是可以互换的。它们都可以用来解决相同的问题,你使用哪一种在很大程度上取决于你的个人喜好 - 你认为哪一种最容易记住或最直观。我们会推荐 for,至少在一开始,因为它可能是最容易记住所有内容的 - 初始化器、条件和最终表达式都必须整齐地放在括号中,因此很容易看到它们在哪里并检查 你并没有想念他们。

¥For other uses, for, while, and do...while loops are largely interchangeable. They can all be used to solve the same problems, and which one you use will largely depend on your personal preference — which one you find easiest to remember or most intuitive. We would recommend for, at least to begin with, as it is probably the easiest for remembering everything — the initializer, condition, and final-expression all have to go neatly into the parentheses, so it is easy to see where they are and check that you aren't missing them.

让我们再看一遍。

¥Let's have a look at them all again.

第一个 for...of

¥First 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

¥and finally do...while:

js
initializer
do {
  // code to run

  final-expression
} while (condition)

注意:还有其他循环类型/功能,它们在高级/特殊情况下很有用,超出了本文的范围。如果你想进一步学习循环,请阅读我们的高级 循环和迭代指南

¥Note: There are other loop types/features too, which are useful in advanced/specialized situations and beyond the scope of this article. If you want to go further with your loop learning, read our advanced Loops and iteration guide.

测试你的技能!

¥Test your skills!

你已读完本文,但你还记得最重要的信息吗?在继续之前,你可以找到一些进一步的测试来验证你是否已保留此信息 - 请参阅 测试你的技能:循环

¥You've reached the end of this article, but can you remember the most important information? You can find some further tests to verify that you've retained this information before you move on — see Test your skills: Loops.

结论

¥Conclusion

本文向你揭示了背后的基本概念,以及在 JavaScript 中循环代码时可用的不同选项。你现在应该清楚为什么循环是处理重复代码的良好机制,并且渴望在自己的示例中使用它们!

¥This article has revealed to you the basic concepts behind, and different options available when looping code in JavaScript. You should now be clear on why loops are a good mechanism for dealing with repetitive code and raring to use them in your own examples!

如果有什么不明白的地方,欢迎再通读一遍文章,或者 联系我们 寻求帮助。

¥If there is anything you didn't understand, feel free to read through the article again, or contact us to ask for help.

也可以看看