Skip to content

回顾 流程控制语句(Day5 1/3) ​

本页对应 Day05_JavaScript,共 7 段视频,约 1 小时 12 分。

学习目标 ​

  1. 能根据给定的 for、while、do...while 代码,写出循环执行次数、每次输出结果及循环结束后计数变量的最终值,并能指出死循环和 continue 导致的输出差异。
  2. 能使用 while 循环及嵌套循环编写四种九九乘法表,正确控制行数、每行单元格数、空白单元格数量和乘法表达式拼接。
  3. 能编写循环程序筛选 100~200 之间能被 3 或 7 整除的数,以及 100~999 中百位、十位或个位含 3 或 7 的三位数,并正确使用取余、取整和逻辑或条件。
  4. 能分别用两层循环和一层循环计算 1!+2!+…+20!,说明累乘变量与累加变量的更新顺序并输出正确结果。
  5. 能说明数组是有序集合、元素、索引从 0 开始、length 等概念,并能用直接量、Array 函数、Array 构造函数三种方式创建空数组、含任意类型元素的数组及指定长度数组,正确解释 Array(17) 与 [17] 的差异。

知识点 ​

从循环执行过程读题:不要背现象 ​

为什么需要:循环题最容易“看着会、做就错”,因为真正考的是程序按顺序执行后变量变成了多少,而不是背某个现象。

怎么想:先找循环变量初值、条件、循环体里的自增/自减和输出位置;再逐步代入。比如 for (var i = 2; i <= 5; i += 3) {} 结束后输出 i:初值 2 成立,执行后变 5;5 仍成立,再变 8;8 不成立,循环结束,所以结果是 8,循环执行 2 次。

怎么做:do...while 要先执行一次再判断。转写中的题:i 初值 10,循环体先 i++ 再输出,条件 i > 10 一直成立,因此会输出 11、12、13……形成死循环。若循环体里既有 i++,for 的更新表达式又写 i++,则一次循环会自增两次:输出 1、3、5、7、9,结束后 i 为 11。

continue 题要看条件变化:输出 1、2、3 后,i 变 4,满足 i > 3 就跳出本次循环;之后 5、6 也继续跳过,直到循环条件不成立。因此只输出 1、2、3。

易错点:把“跳过本次循环”误记成“循环结束”;忽略输出写在循环体内还是循环体外;把 i++ 写了两处还按一处推算。代码要按顺序分析,不能记现象。

while 实现四种九九乘法表 ​

为什么需要:用 while 练循环嵌套、行与列的关系,以及用空单元格控制排版。

怎么想:第一种:外层 i 控制行,1 到 9;内层 j 控制本行单元格数,1 到 i。内容为 j * i = 结果。第二种只是外层从 9 到 1。第三种在每行有内容单元格前加 9 - i 个空 td;第四种再把外层改成 9 到 1。内层公式仍是 j 和 i。

怎么做:核心结构如下(样式略,转写中设置了表格宽度、固定列宽、合并边框、td 边框和内边距):

js
var i = 1;
while (i <= 9) {
  document.write("<tr>");
  var j = 1;
  while (j <= i) {
    document.write("<td>" + j + "*" + i + "=" + (j * i) + "</td>");
    j++;
  }
  document.write("</tr>");
  i++;
}

第三种空白单元格可写:

js
var k = 1;
while (k <= 9 - i) {
  document.write("<td></td>");
  k++;
}

易错点:内外层变量重名会互相干扰;忘记 i++ 或 j++ 会死循环;空单元格里不要写内容;复制代码后要找到真正不同的那一处再改。

整除判断与数位拆分 ​

为什么需要:练习条件组合和“从数字中取百位、十位、个位”的计算思维。

怎么想:100 到 200 中能被 3 或 7 整除,条件是 i % 3 === 0 || i % 7 === 0,用逻辑或。三位数取位:百位 Math.floor(number / 100);十位 Math.floor(number % 100 / 10);个位 number % 10。判断“位上有 3 或 7”,把三位分别与 3、7 比较,再用逻辑或连接。

怎么做:

js
for (var i = 100; i <= 200; i++) {
  if (i % 3 === 0 || i % 7 === 0) {
    console.log(i);
  }
}
js
for (var number = 100; number <= 999; number++) {
  var n1 = Math.floor(number / 100);
  var n2 = Math.floor(number % 100 / 10);
  var n3 = number % 10;
  if (n1 === 3 || n1 === 7 || n2 === 3 || n2 === 7 || n3 === 3 || n3 === 7) {
    console.log(number);
  }
}

易错点:整除用 % 是否为 0;取十位不要忘记先取余再除以 10;多个“或”条件不要误写成“且”;Math.floor 是取整,不是四舍五入。

20 的阶乘和:两层与一层 ​

为什么需要:理解“先求每个数的阶乘,再累加”和“在循环中同步维护阶乘与和”的区别。

怎么想:两层循环更直观:外层 number 从 1 到 20,内层求 number 的阶乘,再把结果加到 sum。一层循环利用一个性质:res 在上一轮已经是 (n-1)!,本轮 res *= n 就得到 n!,随后 sum += res。

怎么做:

js
var sum = 0;
for (var number = 1; number <= 20; number++) {
  var res = 1;
  for (var i = 1; i <= number; i++) {
    res *= i;
  }
  sum += res;
}
js
var sum2 = 0, res2 = 1;
for (var n = 1; n <= 20; n++) {
  res2 *= n;
  sum2 += res2;
}

易错点:求积变量初值必须是 1,求和变量初值是 0;两层写法中内层 res 每轮要重置;一层写法中 res 不能重置,否则会变成只算最后一个阶乘。

数组概念与三种创建方式 ​

为什么需要:数组能把多个数据组合成一个整体,属于对象类型,比原始类型承载的信息更多。

怎么想:数组是值的有序集合,成员叫元素,每个元素有位置叫索引(下标),索引从 0 开始且连续。元素可以是任意类型,也可以是数组。

怎么做:三种创建方式:直接量、Array() 函数、new Array() 构造函数。

js
var arr1 = [];
var arr2 = [100, 200, 300, [1, 2]];
var arr3 = Array();
var arr4 = Array(1, 2, 3);
var arr5 = new Array(1, 2, 3);

易错点:Array(17) 只写一个数字参数时,不是创建元素为 17 的数组,而是创建长度为 17 的空数组;[17] 才是一个元素 17。typeof 数组得到的是 "object",不能靠它区分数组和普通对象。

本节要回顾什么 ​

本节笔记很短,核心是“流程控制语句”中的两类:循环语句和跳转语句。先把原笔记保留如下:

text
1. 循环语句
   while 循环
   do while 循环
   for 循环
 
2. 跳转语句
   break
   continue

为什么需要回顾?因为后面写九九乘法表、判断 100 到 200 之间能被 3 或 7 整除的数、找位上有 3 或 7 的三位数、求 20 的阶乘和,都离不开循环和跳转。怎么想?不要背“某题输出什么”,而要代入循环变量,按代码顺序分析。怎么做?抓住三件事:循环变量从哪里开始、条件何时成立、循环体里有没有改变量。易错点是把 continue 当 break,或者循环体里重复自增、忘记自增。

while 循环:先判断,再执行 ​

为什么需要?当我们不知道要循环多少次,或者只知道“条件还成立就继续”时,while 很合适。怎么想?先写循环变量初始值,再写条件,最后别忘了在循环体里更新变量。怎么做?转写稿中四种九九乘法表用 while 实现:外层控制行数,内层控制本行单元格数量;外层变量从 1 到 9,内层变量从 1 到当前行号。内层变量名不能和外层重复,否则嵌套执行时会“混了”,造成程序执行错乱。易错点:忘记 i++ 会死循环;嵌套循环里变量名重复会互相覆盖。

do while 循环:先执行一次,再判断 ​

为什么需要?有些逻辑要求“无论如何先做一次”,这时用 do while。怎么想?它一上来先进入 do,执行完才判断条件。怎么做?看视频1第二题:变量初始是 10,进入 do 后先 i++,变成 11,再输出;条件写的是 i > 10,11 大于 10 成立,于是继续循环,下一次变成 12 再输出……所以不是只输出一个 11,而是输出 10、11、12……形成死循环。转写稿里“先加加还是先输出”的疑问,答案看代码顺序:先写哪个就先执行哪个。易错点:把 do while 当成普通 while,忽略“先执行一次”;条件方向写反也会造成死循环。

for 循环:初始化、条件、更新写在一起 ​

为什么需要?循环次数比较明确时,for 更紧凑。怎么想?把“起点、终点、步长”写进小括号,循环体只放重复逻辑。怎么做?视频1第三题中,for 的小括号里已有一次自增,循环体里又写了一次自增,于是变量一次加 2:第一次输出 1,第二次输出 3,接着 5、7、9;9 之后还会再加两次,循环结束时变量定格在 11。视频3判断 100 到 200 之间能被 3 或 7 整除的数,思路是 for 从 100 循环到 200,循环体里用 if (i % 3 === 0 || i % 7 === 0) 判断并输出。易错点:循环体里重复自增;把“或”写成“与”;循环结束后还误以为变量等于最后一个输出值。

break 和 continue:改变循环走向 ​

为什么需要?有时要提前结束整个循环,有时只想跳过本次。怎么想?break 是“整个循环不做了”,continue 是“本次到此为止,回去做下一次”。怎么做?视频1第四题中,输出写在循环体里,当 i > 3 时执行 continue:1、2、3 正常输出;到 4 时大于 3,跳过本次;5、6 也大于 3,继续跳过;循环结束后只输出 1、2、3。注意转写稿提醒:昨天例子可能是“等于几”,今天换成“大于 3”,条件一变结果就变,不能记现象。易错点:把 continue 当 break;continue 后面的代码本次不会执行;条件写错会导致该跳过的没跳过。

调试与写法建议 ​

调试时最有效的方法是 console.log 打出循环变量,按轮次代入。比如第一题:i 初始 2,条件 i <= 5,每次加 3,第一轮变 5,第二轮变 8,第三轮条件不成立,所以循环两次,最后 i 是 8。写嵌套循环时,内外层变量名要区分;写 while 时先检查有没有更新;写 continue 时先问自己“是跳过本次还是结束整个循环”。总结:流程控制语句不难,难在按程序顺序分析,而不是背答案。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

示例一:while 循环实现四种九九乘法表 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>while 循环实现四种九九乘法表</title>
<style>
  table {
    width: 900px;
    table-layout: fixed;      /* 固定列宽 */
    border-collapse: collapse; /* 相邻边框合并 */
  }
  td {
    border: 1px solid #999;
    padding: 4px;
  }
  td:empty { border: none; }   /* 没有内容的单元格不显示边框 */
</style>
</head>
<body>
<script>
  var i, j;

  /* ---------- 第一种:正三角 ---------- */
  document.write('<h2>第一种九九乘法表</h2>');
  document.write('<table>');
  i = 1;                       // 标记变量初始化写到循环外边
  while (i <= 9) {
    document.write('<tr>');
    j = 1;
    while (j <= i) {           // 第 i 行有 i 个单元格
      document.write('<td>' + j + '×' + i + '=' + (j * i) + '</td>');
      j++;
    }
    document.write('</tr>');
    i++;
  }
  document.write('</table>');

  /* ---------- 第二种:倒三角(只改外层) ---------- */
  document.write('<h2>第二种九九乘法表</h2>');
  document.write('<table>');
  i = 9;
  while (i >= 1) {             // 9 到 1
    document.write('<tr>');
    j = 1;
    while (j <= i) {
      document.write('<td>' + j + '×' + i + '=' + (j * i) + '</td>');
      j++;
    }
    document.write('</tr>');
    i--;                       // 自减
  }
  document.write('</table>');

  /* ---------- 第三种:右对齐(前面补空白单元格) ---------- */
  document.write('<h2>第三种九九乘法表</h2>');
  document.write('<table>');
  i = 1;
  while (i <= 9) {
    document.write('<tr>');
    j = 1;
    while (j <= 9 - i) {       // 先输出 9-i 个空单元格
      document.write('<td></td>');
      j++;
    }
    j = 1;
    while (j <= i) {           // 再输出 i 个有内容的单元格
      document.write('<td>' + j + '×' + i + '=' + (j * i) + '</td>');
      j++;
    }
    document.write('</tr>');
    i++;
  }
  document.write('</table>');

  /* ---------- 第四种:倒三角 + 右对齐 ---------- */
  document.write('<h2>第四种九九乘法表</h2>');
  document.write('<table>');
  i = 9;
  while (i >= 1) {
    document.write('<tr>');
    j = 1;
    while (j <= 9 - i) {
      document.write('<td></td>');
      j++;
    }
    j = 1;
    while (j <= i) {
      document.write('<td>' + j + '×' + i + '=' + (j * i) + '</td>');
      j++;
    }
    document.write('</tr>');
    i--;
  }
  document.write('</table>');
</script>
</body>
</html>

关键行与易错点

  • i 表示行号,j 表示每行单元格编号;内层循环的标记变量必须和外层不同名,否则内外层互相覆盖,程序执行错乱。
  • i++、j++ 一定要写在循环体里,漏写直接死循环;用 while 时初始化(i = 1)必须写在循环外边。
  • 第二种只改外层三处:初值 9、条件 i >= 1、i--,内层 j <= i 和拼接公式一个字都不用动。
  • 第三种先补 9 - i 个空 <td></td>,再输出 i 个有内容的单元格;空 td 里连空格都不能有,否则 td:empty 不匹配,边框会露出来。
  • 公式是“j × i”:第一个乘数是内层变量(每行从 1 递增),第二个乘数是外层变量(行号)。

示例二:能被 3 或 7 整除的数 & 位上有 3 或 7 的三位数 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>循环判断练习</title>
</head>
<body>
<script>
  /* ---------- 1. 100~200 之间能被 3 或 7 整除的数 ---------- */
  var str = '';
  for (var i = 100; i <= 200; i++) {
    if (i % 3 === 0 || i % 7 === 0) {   // 只要有一个成立即可
      str += i + ' ';
    }
  }
  document.write('<p>100~200 之间能被 3 或 7 整除的数:<br>' + str + '</p>');

  /* ---------- 2. 三位数中,百位 / 十位 / 个位有 3 或 7 的数 ---------- */
  var result = '';
  for (var number = 100; number <= 999; number++) {
    var n1 = parseInt(number / 100);        // 百位:8.59 → 8
    var n2 = parseInt(number % 100 / 10);   // 十位:859 % 100 = 59 → 5.9 → 5
    var n3 = number % 10;                   // 个位:859 % 10 = 9
    if (n1 === 3 || n1 === 7 ||
        n2 === 3 || n2 === 7 ||
        n3 === 3 || n3 === 7) {
      result += number + ' ';
    }
  }
  document.write('<p>三位数中某一位上有 3 或 7 的数:<br>' + result + '</p>');
</script>
</body>
</html>

关键行与易错点

  • 整除判断用 i % 3 === 0,两个条件之间是逻辑或 ||,写成 && 就一个数也输出不了。
  • parseInt() 是直接取整数部分(不四舍五入),所以 parseInt(859 / 100) 得到 8 而不是 9。
  • 十位不能直接写 parseInt(number / 10),那样拿到的是 85;要先用 % 100 去掉整百,再 / 10 取整。
  • 个位可以直接 number % 10:100 是 10 的倍数,先 %100 再 %10 可以简化成一次 %10。
  • 百位、十位、个位三组判断之间仍然是 ||——不管哪一位上有 3 或 7 都算符合。

示例三:20 的阶乘和(两种写法)+ 数组的三种创建方式 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>20 的阶乘和</title>
</head>
<body>
<script>
  /* ---------- 写法一:两层循环 ---------- */
  var sum = 0;                              // 记录和
  for (var number = 1; number <= 20; number++) {
    var res = 1;                            // 记录乘积,必须在内层循环前重置
    for (var i = 1; i <= number; i++) {
      res *= i;                             // res = res * i
    }
    sum += res;
  }
  console.log('两层循环 —— 20 的阶乘和:' + sum);

  /* ---------- 写法二:一层循环 ---------- */
  var sum1 = 0, res1 = 1;                   // 两个变量都只定义一次
  for (var n = 1; n <= 20; n++) {
    res1 *= n;                              // 上一次的阶乘 × n = 当前阶乘
    sum1 += res1;                           // 算出一个就加一个
  }
  console.log('一层循环 —— 20 的阶乘和:' + sum1);

  /* ---------- 数组的三种创建方式 ---------- */
  var arr1 = [100, 200, 300];               // 1. 直接量方式(最常用)
  var arr2 = Array('hello', true, 1);       // 2. Array 函数方式
  var arr3 = new Array(5);                  // 3. Array 构造函数方式
  var arr4 = [17];                          // 直接量:1 个元素,值为 17

  console.log(arr1);          // [100, 200, 300]
  console.log(arr2);          // ["hello", true, 1]
  console.log(arr3);          // 5 个空元素,length 是 5
  console.log(arr3.length);   // 5
  console.log(arr4, arr4.length); // [17]  1
  console.log(typeof arr1);   // "object"(typeof 区分不出数组)
</script>
</body>
</html>

关键行与易错点

  • 两层写法里 var res = 1 必须写在外层循环体内、内层循环之前;写在外边会拿上一次的阶乘继续乘,结果全错。
  • 一层写法的精髓:res1 只定义一次,res1 *= n 时它保存的正是“上一个数的阶乘”,乘完 n 就是 n 的阶乘,随即 sum1 += res1 累加。
  • 阶乘增长极快(100 的阶乘已经是 158 位的大数),所以题目只取到 20;循环起点从 1 开始,写 0 就全变成 0 了。
  • Array(17) 只有一个数字参数时,这个参数表示元素个数,创建 17 个“有位置没值”的空元素;而 [17] 是 1 个元素、值为 17——这是最典型的坑。
  • new Array() 和 Array() 的结果没有任何区别,只是多了一个 new;实际开发中首选直接量 []。
  • typeof 数组返回 "object",不能靠它判断是不是数组。

常见错误 ​

  1. 现象:do…while 那段代码运行后输出 11、12、13……停不下来。 原因:do 先执行一次循环体,循环体里先做了自增,条件 i > 10 之后每一轮都成立,循环没有收敛的方向。 修法:写 do…while 前先把"初值 / 条件 / 步进"三件事列出来,确认步进会让条件最终为假;条件方向写反的,改成 i <= 10 或把自增换成自减。

  2. 现象:for 循环本该输出 1~10,结果只输出 1、3、5、7、9,结束后变量是 11 而不是 10。 原因:for 的第三个表达式已经做了一次自增,循环体里又写了一次,步长变成 2。 修法:自增只保留一处;写完数一遍"一次迭代里这个变量被改了几次"。

  3. 现象:if (i > 3) continue; 的题只输出 123,就判断"循环在 4 处结束了"。 原因:把 continue 当成 break 记成了固定现象,没有按代码逐行代入;4 之后的 5、6 仍会进入循环,只是每次都被 continue 跳过输出。 修法:遇到跳转语句,逐次列出 i 的值与"是否进入判断 / 是否执行输出"三列小表,再下结论。

  4. 现象:嵌套循环做九九乘法表时,行数或每行单元格数乱掉,甚至只输出一行。 原因:内外层用了同一个循环变量名,内层把外层的计数改掉了。 修法:外层用 i、内层用 j(或语义化命名),并确认内层次数的上界来自外层变量。

  5. 现象:用 while 输出 1~9 时页面卡死。 原因:只在 for 里习惯写自增,换成 while 后忘了在循环体里改变循环变量,条件恒为真。 修法:while 的循环体最后一行固定写步进;写完先口算一次"第二轮条件还成立吗"。

  6. 现象:new Array(17) 想得到"只有一个元素 17 的数组",结果 length 是 17,元素全是 empty。 原因:Array 只传一个数字参数时,该参数被当成元素个数;换成字面量 [17] 就只是一个元素。 修法:优先用 [] 字面量;确实要用 Array,就记住"单数字参数 = 长度"这条例外,并在控制台用 length 验证。

  7. 现象:typeof arr 得到 "object",于是判断"这不是数组"。 原因:typeof 只对原始类型给出细分结果,数组属于对象类型。 修法:判断数组用 Array.isArray(arr);同时记住 length 和索引 0、1、2… 是查看数组内容最直接的方式。

  8. 现象:取三位数的百位得到 8.59,十位取出来是 59 而不是 5。 原因:除法结果带小数没有取整;取余拿到的是"剩下的两位数",还没继续除 10。 修法:每一步取位都问自己"现在拿到的是几个整百 / 整十",缺取整就补 parseInt/Math.floor,缺除 10 就补上。

练习 ​

  1. 读代码写结果:给出三段循环(含 do…while 先自增、for 体内重复自增、for + continue),分别写出控制台输出和循环结束后计数变量的值。要求:每段附上"逐次迭代表",不许只写答案。

  2. 改错题:提供一段"用 while 输出 1~9 行、每行 i 个单元格"的代码,其中内层变量名与外层相同、while 体内漏了步进。要求:指出两处错误并说明各自会导致什么现象,改完后在浏览器验证。

  3. 计数改造:把"100~200 之间能被 3 或 7 整除的数"改成"输出这些数,并统计一共多少个"。要求:累加变量定义在循环外,输出语句放在循环结束后。

  4. 位上有 3 或 7 的三位数:先统计满足条件的数共有多少个,再额外输出其中"百位是 7"的那些数。要求:取位部分单独写成三行并各自 console.log 验证一次。

  5. 20 的阶乘和:分别用两层循环和一层循环实现,输出两个结果并比较是否相等。要求:写三句话说明一层循环里 res 为什么不需要每轮重置,而两层循环里必须重置。

  6. 数组创建对照:用字面量、Array 函数、Array 构造函数三种方式各创建一个"含 3 个元素"的数组和一个"长度为 3 的空数组",打印 length 和 typeof,并说明哪种写法最不容易出错。

原视频对照 ​

#视频时长
1题目讲解11:38
2while循环实现四种九九乘法表11:54
3能被3或7整除的数01:45
4位上有3或7的三位数09:15
520的阶乘和13:07
6数组的概念06:21
7创建数组的三种方式18:36

小结 ​

  1. 循环题先定三要素再写代码:初值、让条件最终为假的条件、每次迭代改变谁的步进;三者缺一就是死循环。
  2. 判断循环结果靠"代入一次迭代"的手工推演,不靠记忆上次讲过什么现象——条件表达式一改,结论就全变。
  3. break 与 continue 的区分点只有一个:后续迭代是否还会重新判断条件并进入循环体。
  4. 嵌套循环的两条铁律:内外层变量不同名;内层次数由上层的当前值决定。
  5. 取位的通用套路是"整除取整拿高位、取余砍高位、再除再取整",每一步都要能说出"这是几个整百/整十"。
  6. 累积型变量(和、积、计数)一律定义在循环外,且循环体内只能"叠加",不能重新赋初值。
  7. 数组是对象,typeof 给不出数组身份,创建时优先用 [];Array(数字) 是长度而非元素,是必须记住的例外。
  8. 先让需求跑通,再谈把八行压成五行;优化是完成后有余力才做的事,不是初学阶段的验收标准。