精讲 JavaScript 循环:while 和 for

简介: 精讲 JavaScript 循环:while 和 for

640.png


循环:while 和 for


我们经常需要重复执行一些操作。

例如,我们需要将列表中的商品逐个输出,或者运行相同的代码将数字 1 到 10 逐个输出。

循环 是一种重复运行同一代码的方法。


"while" 循环


while 循环的语法如下:


while (condition) {
  // 代码
  // 所谓的“循环体”
}


conditiontrue 时,执行循环体的 code

例如,以下将循环输出当 i < 3 时的 i 值:


let i = 0;
while (i < 3) { // 依次显示 0、1 和 2
  alert( i );
  i++;
}


循环体的单次执行叫作 一次迭代。上面示例中的循环进行了三次迭代。

如果上述示例中没有 i++,那么循环(理论上)会永远重复执行下去。实际上,浏览器提供了阻止这种循环的方法,我们可以通过终止进程,来停掉服务器端的 JavaScript。

任何表达式或变量都可以是循环条件,而不仅仅是比较。在 while 中的循环条件会被计算,计算结果会被转化为布尔值。

例如,while (i != 0) 可简写为 while (i)


let i = 3;
while (i) { // 当 i 变成 0 时,条件为 false,循环终止
  alert( i );
  i--;
}


单行循环体不需要大括号:

如果循环体只有一条语句,则可以省略大括号 {…}


let i = 3;
while (i) alert(i--);


"do..while" 循环

使用 do..while 语法可以将条件检查移至循环体 下面


do {
  // 循环体
} while (condition);


循环首先执行循环体,然后检查条件,当条件为真时,重复执行循环体。

例如:


let i = 0;
do {
  alert( i );
  i++;
} while (i < 3);


这种形式的语法很少使用,除非你希望不管条件是否为真,循环体 至少执行一次。通常我们更倾向于使用另一个形式:while(…) {…}


"for" 循环


for 循环更加复杂,但它是最常使用的循环形式。

for 循环看起来就像这样:


for (begin; condition; step) {
  // ……循环体……
}


我们通过示例来了解一下这三个部分的含义。下述循环从 i 等于 03(但不包括 3)运行 alert(i)


for (let i = 0; i < 3; i++) { // 结果为 0、1、2
  alert(i);
}


我们逐个部分分析 for 循环:


语句段

begin i = 0 进入循环时执行一次。
condition i < 3 在每次循环迭代之前检查,如果为 false,停止循环。
body(循环体) alert(i) 条件为真时,重复运行。
step i++ 在每次循环体迭代后执行。


一般循环算法的工作原理如下:


开始运行
→ (如果 condition 成立 → 运行 body 然后运行 step)
→ (如果 condition 成立 → 运行 body 然后运行 step)
→ (如果 condition 成立 → 运行 body 然后运行 step)
→ ...


所以,begin 执行一次,然后进行迭代:每次检查 condition 后,执行 bodystep

如果你这是第一次接触循环,那么回到这个例子,在一张纸上重现它逐步运行的过程,可能会对你有所帮助。


以下是在这个示例中发生的事:


// for (let i = 0; i < 3; i++) alert(i)
// 开始
let i = 0
// 如果条件为真,运行下一步
if (i < 3) { alert(i); i++ }
// 如果条件为真,运行下一步
if (i < 3) { alert(i); i++ }
// 如果条件为真,运行下一步
if (i < 3) { alert(i); i++ }
// ……结束,因为现在 i == 3


内联变量声明

这里“计数”变量 i 是在循环中声明的。这叫做“内联”变量声明。这样的变量只在循环中可见。


for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // 错误,没有这个变量。


除了定义一个变量,我们也可以使用现有的变量:


let i = 0;
for (i = 0; i < 3; i++) { // 使用现有的变量
  alert(i); // 0, 1, 2
}
alert(i); //3,可见,因为是在循环之外声明的


省略语句段


for 循环的任何语句段都可以被省略。

例如,如果我们在循环开始时不需要做任何事,我们就可以省略 begin 语句段。

就像这样:


let i = 0; // 我们已经声明了 i 并对它进行了赋值
for (; i < 3; i++) { // 不再需要 "begin" 语句段
  alert( i ); // 0, 1, 2
}


我们也可以移除 step 语句段:


let i = 0;
for (; i < 3;) {
  alert( i++ );
}


该循环与 while (i < 3) 等价。

实际上我们可以删除所有内容,从而创建一个无限循环:


for (;;) {
  // 无限循环
}


请注意 for 的两个 ; 必须存在,否则会出现语法错误。


跳出循环

通常条件为假时,循环会终止。

但我们随时都可以使用 break 指令强制退出。

例如,下面这个循环要求用户输入一系列数字,在输入的内容不是数字时“终止”循环。


let sum = 0;
while (true) {
  let value = +prompt("Enter a number", '');
  if (!value) break; // (*)
  sum += value;
}
alert( 'Sum: ' + sum );


如果用户输入空行或取消输入,在 (*) 行的 break 指令会被激活。它立刻终止循环,将控制权传递给循环后的第一行,即,alert

根据需要,"无限循环 + break" 的组合非常适用于不必在循环开始/结束时检查条件,但需要在中间甚至是主体的多个位置进行条件检查的情况。


继续下一次迭代


continue 指令是 break 的“轻量版”。它不会停掉整个循环。而是停止当前这一次迭代,并强制启动新一轮循环(如果条件允许的话)。

如果我们完成了当前的迭代,并且希望继续执行下一次迭代,我们就可以使用它。

下面这个循环使用 continue 来只输出奇数:


for (let i = 0; i < 10; i++) {
  //如果为真,跳过循环体的剩余部分。
  if (i % 2 == 0) continue;
  alert(i); // 1,然后 3,5,7,9
}


对于偶数的 i 值,continue 指令会停止本次循环的继续执行,将控制权传递给下一次 for 循环的迭代(使用下一个数字)。因此 alert 仅被奇数值调用。


continue 指令利于减少嵌套:

显示奇数的循环可以像下面这样:


for (let i = 0; i < 10; i++) {
  if (i % 2) {
    alert( i );
  }
}


从技术角度看,它与上一个示例完全相同。当然,我们可以将代码包装在 if 块而不使用 continue

但在副作用方面,它多创建了一层嵌套(大括号内的 alert 调用)。如果 if 中代码有多行,则可能会降低代码整体的可读性。


注意:禁止 break/continue 在‘?’的右边


请注意非表达式的语法结构不能与三元运算符 ? 一起使用。特别是 break/continue 这样的指令是不允许这样使用的。

例如,我们使用如下代码:


if (i > 5) {
  alert(i);
} else {
  continue;
}


……用问号重写:


(i > 5) ? alert(i) : continue; // continue 不允许在这个位置


……代码会停止运行,并显示有语法错误。

这只是不使用 ? 而不是 if 的另一个原因。


break/continue 标签


有时候我们需要从一次从多层嵌套的循环中跳出来。

例如,下述代码中我们的循环使用了 ij,从 (0,0)(3,3) 提示坐标 (i, j)


for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    let input = prompt(`Value at coords (${i},${j})`, '');
    // 如果我想从这里退出并直接执行 alert('Done!')
  }
}
alert('Done!');


我们需要提供一种方法,以在用户取消输入时来停止这个过程。

input 之后的普通 break 只会打破内部循环。这还不够 —— 标签可以实现这一功能!

标签 是在循环之前带有冒号的标识符:


labelName: for (...) {
  ...
}


break <labelName> 语句跳出循环至标签处:


outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    let input = prompt(`Value at coords (${i},${j})`, '');
    // 如果是空字符串或被取消,则中断并跳出这两个循环。
    if (!input) break outer; // (*)
    // 用得到的值做些事……
  }
}
alert('Done!');


上述代码中,break outer 向上寻找名为 outer 的标签并跳出当前循环。

因此,控制权直接从 (*) 转至 alert('Done!')

我们还可以将标签移至单独一行:


outer:
for (let i = 0; i < 3; i++) { ... }


continue 指令也可以与标签一起使用。在这种情况下,执行跳转到标记循环的下一次迭代。


注意:标签并不允许“跳到”所有位置

标签不允许我们跳到代码的任意位置。

例如,这样做是不可能的:


break label;  // 无法跳转到这个标签
label: for (...)


只有在循环内部才能调用 break/continue,并且标签必须位于指令上方的某个位置。


总结


我们学习了三种循环:

  • while —— 每次迭代之前都要检查条件。
  • do..while —— 每次迭代后都要检查条件。
  • for (;;) —— 每次迭代之前都要检查条件,可以使用其他设置。

通常使用 while(true) 来构造“无限”循环。这样的循环和其他循环一样,都可以通过 break 指令来终止。

如果我们不想在当前迭代中做任何事,并且想要转移至下一次迭代,那么可以使用 continue 指令。

break/continue 支持循环前的标签。标签是 break/continue 跳出嵌套循环以转到外部的唯一方法。


作业题


先自己做题目再看答案。


1. 最后一次循环的值


重要程度:⭐️⭐️⭐

此代码最后一次 alert 值是多少?为什么?


let i = 3;
while (i) {
  alert( i-- );
}


答案:

结果是 1


let i = 3;
while (i) {
  alert( i-- );
}


每次循环迭代都将 i1。当检查到 i = 0 时,while(i) 循环停止。

因此,此循环执行的步骤如下(“循环展开”):


let i = 3;
alert(i--); // 显示 3,i 减至 2
alert(i--) // 显示 2,i 减至 1
alert(i--) // 显示 1,i 减至 0
// 完成,while(i) 检查循环条件并停止循环


2. while 循环显示哪些值?

重要程度:⭐️⭐️⭐️⭐

对于每次循环,写出你认为会显示的值,然后与答案进行比较。

一下两个循环的 alert 值是否相同?

  1. 前缀形式 ++i:


let i = 0;
while (++i < 5) alert( i );


  1. 后缀形式 i++


let i = 0;
while (i++ < 5) alert( i );


答案:

这个题目展现了 i++/++i 两种形式在比较中导致的不同结果。


  1. 从 1 到 4:


let i = 0;
while (++i < 5) alert( i );


第一个值是 i = 1,因为 ++i 首先递增 i 然后返回新值。因此先比较 1 < 5 然后通过 alert 显示 1

然后按照 2, 3, 4… —— 数值一个接着一个被显示出来。在比较中使用的都是递增后的值,因为 ++ 在变量前。

最终,i = 4 时,在 ++i < 5 的比较中,i 值递增至 5,所以 while(5 < 5) 不符合循环条件,循环停止。所以没有显示 5


  1. 从 1 到 5:


let i = 0;
while (i++ < 5) alert( i );


第一个值也是 i = 1。后缀形式 i++ 递增 i 然后返回 值,因此比较 i++ < 5 将使用 i = 0(与 ++i < 5 不同)。

alert 调用是独立的。这是在递增和比较之后执行的另一条语句。因此它得到了当前的 i = 1

接下来是 2, 3,4…

我们在 i = 4 时暂停,前缀形式 ++i 会递增 i 并在比较中使用新值 5。但我们这里是后缀形式 i++。因此,它将 i 递增到 5,但返回旧值。因此实际比较的是 while(4 < 5) —— true,程序继续执行 alert

i = 5 是最后一个值,因为下一步比较 while(5 < 5) 为 false。


3. "for" 循环显示哪些值?


重要程度:⭐️⭐️⭐️⭐

对于每次循环,写下它将显示的值。然后与答案进行比较。

两次循环 alert 值是否相同?

  1. 后缀形式:


for (let i = 0; i < 5; i++) alert( i );


  1. 前缀形式:


for (let i = 0; i < 5; ++i) alert( i );


答案:


在这两种情况下都是从 04


for (let i = 0; i < 5; ++i) alert( i );
for (let i = 0; i < 5; i++) alert( i );


这可以很容易地从 for 算法中推导出:

  1. 在一切开始之前执行 i = 0
  2. 检查 i < 5 条件
  3. 如果 true —— 执行循环体并 alert(i),然后进行 i++

递增 i++ 与检查条件(2)分开。这只是另一种写法。

在这没使用返回的低增值,因此 i++++i之间没有区别。


4. 使用 for 循环输出偶数


重要程度:⭐️⭐️⭐️⭐️⭐️

使用 for 循环输出从 210 的偶数。


答案:

for (let i = 2; i <= 10; i++) {
  if (i % 2 == 0) {
    alert( i );
  }
}


我们使用 "modulo" 运算符 % 来获取余数,并检查奇偶性。


5. 用 "while" 替换 "for"


重要程度:⭐️⭐️⭐️⭐️⭐️

重写代码,在保证不改变其行为的情况下,将 for 循环更改为 while(输出应保持不变)。


for (let i = 0; i < 3; i++) {
  alert( `number ${i}!` );
}


答案:


let i = 0;
while (i < 3) {
  alert( `number ${i}!` );
  i++;
}


6. 重复输入,直到正确为止


重要程度:⭐️⭐️⭐️⭐️⭐️


编写一个提示用户输入大于 100 的数字的循环。如果用户输入其他数值 —— 请他重新输入。

循环一直在请求一个数字,直到用户输入了一个大于 100 的数字,取消输入或输入了一个空行为止。

在这我们假设用户只会输入数字。在本题目中,不需要对非数值输入进行特殊处理。


答案:


let num;
do {
  num = prompt("Enter a number greater than 100?", 0);
} while (num <= 100 && num);


两个检查都为真时,继续执行 do..while 循环:

  1. 检查 num <= 100 —— 即输入值仍然不大于 100
  2. numnull 或空字符串时,&& num 的结果为 false。name while 循环也会停止。

P.S. 如果 numnull,那么 num <= 100true。因此用户单击取消,如果没有第二次检查,循环就不会停止。两次检查都是必须的。


7. 输出素数(prime)


重要程度:⭐️⭐️⭐


大于 1 且不能被除了 1 和它本身以外的任何数整除的整数叫做素数[1]

换句话说,n > 1 且不能被 1n 以外的任何数整除的整数,被称为素数。

例如,5 是素数,因为它不能被 234 整除,会产生余数。

写一个可以输出 2n 之间的所有素数的代码。


n = 10,结果输出 2、3、5、7

P.S. 代码应适用于任何 n,而不是对任何固定值进行硬性调整。


答案:


这个题目有很多解法。

我们使用一个嵌套循环:


对于间隔中的每个 i {
  检查在 1~i 之间,是否有 i 的除数
  如果有 => 这个 i 不是素数
  如果没有 => 这个 i 是素数,输出出来
}


使用标签的代码:


let n = 10;
nextPrime:
for (let i = 2; i <= n; i++) { // 对每个自然数 i
  for (let j = 2; j < i; j++) { // 寻找一个除数……
    if (i % j == 0) continue nextPrime; // 不是素数,则继续检查下一个
  }
  alert( i ); // 输出素数
}


这段代码有很大的优化空间。例如,我们可以从 2i 的平方根之间的数中寻找除数。无论怎样,如果我们想要在很大的数字范围内实现高效率,我们需要改变实现方法,依赖高等数学和复杂算法,如二次筛选法(Quadratic sieve)[2]普通数域筛选法(General number field sieve)[3]等。

目录
相关文章
|
3月前
|
JavaScript 前端开发
JS循环for、for...of、for...in
本文介绍了JavaScript中不同的循环语句,包括传统的`for`循环、`for...of`循环用于遍历数组和类数组对象、`for...in`循环用于遍历对象的属性,并通过示例代码展示了它们的用法和区别。
51 6
JS循环for、for...of、for...in
|
3月前
|
JavaScript 前端开发
JavaScript基础知识-流程控制之while循环
这篇文章介绍了JavaScript中的while循环和do...while循环的基础知识,并通过一个实际案例演示了如何使用while循环计算投资增长到特定金额所需的年数。
55 2
JavaScript基础知识-流程控制之while循环
|
2月前
|
JavaScript 前端开发
js循环有几种
js循环有几种
32 0
|
25天前
|
JavaScript
js动画循环播放特效源码(上班族的一天)
js动画循环播放特效是一段实现了包含形象的卡通小人吃、睡、电脑工作的网页动画,js循环动画,简单的画面设计。非常丝滑有意思,欢迎对此代码感兴趣的朋友前来下载参考。
27 2
|
3月前
|
前端开发 JavaScript
前端基础(八)_JavaScript循环(for循环、for-in循环、for-of循环、while、do-while 循环、break 与 continue)
本文介绍了JavaScript中的循环语句,包括for循环、for-in循环、for-of循环、while循环、do-while循环以及break和continue的使用。
67 1
前端基础(八)_JavaScript循环(for循环、for-in循环、for-of循环、while、do-while 循环、break 与 continue)
|
3月前
|
JavaScript 前端开发
JavaScript基础知识-forEach循环
关于JavaScript基础知识中forEach循环的介绍。
46 1
JavaScript基础知识-forEach循环
|
3月前
|
JavaScript 前端开发 索引
|
2月前
|
JavaScript
自动循环提交js
自动循环提交js
18 0
|
3月前
|
JavaScript
js 循环数组取值
js 循环数组取值
|
3月前
|
JavaScript 前端开发
JavaScript while 循环
JavaScript while 循环
17 3