# JavaScript教程 - 6 流程控制

# 6.1 代码块

代码块(Block) 是由一对大括号 {} 包围的零条或多条语句的集合。

举个栗子:

下面的代码是两个代码块:

{
  let a = 2;
  console.log(a);  // 2
}

{
  let b = 5;
  console.log(b);  // 5
}
1
2
3
4
5
6
7
8
9

在前面介绍变量的时候,说过声明变量可以使用 let 或者 varvar 没有块级作用域,let 有块级作用域。

什么意思呢?

就是使用 let 声明的变量,只能在当前的代码块中进行使用:

{
  let a = 2;
}

console.log(a);  // 报错,提示a没有定义
1
2
3
4
5

var 声明的变量,没有块级作用域,都可以访问:

{
  var a = 2;
}
console.log(a);  // 可以访问a,2

{
  console.log(a);  // 可以访问a,2
}
1
2
3
4
5
6
7
8

# 6.2 分支结构

正常情况下,我们的代码都是顺序执行的,一句一句从上到下执行。但是不可能什么情况下都是顺序执行,例如判断在生活中无处不在,我们登录某个系统,需要判断密码是否正确,正确才能登录,否则登录失败。

# 1 if-else 语句

在 JS 中通过 if else 来进行条件的判断,格式如下:

if (condition) {
  // condition为true时执行的代码块
} else {
  // condition为false时执行的代码块
}
1
2
3
4
5

也可以不需要 else

if (condition) {
    // 条件成立,要做的事情
}
1
2
3

if 语句首先会对 condition 表达式进行求值判断,结果为 boolean 值,也就是 truefalse

如果为 condition 表达式的结果为 true,则执行 if 后的语句。如果 condition 表达式为 false 且有 else 代码块,则执行 else 代码块,如果没有 else,那么什么都不执行了。

举个栗子,判断一个数是否为偶数:

let num = 5;
if (num % 2 == 0) {  // 除以2余数为0,是偶数
  console.log(num + " 是偶数");
} else {
  console.log(num + " 是奇数");
}
1
2
3
4
5
6

执行结果:

5 是奇数


如果 condition 表达式不是 boolean 值,则会自动转换为 boolean 值,所以可以对任何数值进行判断:

let a = 5;
let b;  // 未初始化undefined

if (a) {
  console.log("a 被转换为了 true");
}

if (!b) {  // 进行了boolean取反
  console.log("b 被转换为了 false");
}
1
2
3
4
5
6
7
8
9
10
  • 根据前面数据类型转换,5 转换为 trueundefined 转换为 false

一般情况下,我们经常通过 关系运算逻辑运算 作为 if 的判断条件。

举个栗子:

let a = 5;
if (a > 0 && a < 10)
  console.log("a在0~10之间");
1
2
3

如果 ifelse 后面只有一条语句,可以省略 {} ,但是建议加上,结构更清晰。

# 2 if-elseif-else 语句

if-else 只能进行是和否的判断。

if-elseif-else 可以进行多条件判断。

语法格式:

if (condition1) {
  // condition1为true时执行的语句块
} else if (condition2) {
  // condition2为true时执行的语句块
} else {
  // 所有条件都为false时执行的语句块
}
1
2
3
4
5
6
7

else-if 可以有0个或多个,最后的 else 可以省略。

当满足一个条件后,其他的条件就不会再判断了,如果所有条件都不满足,会执行 else


下面通过练习来举个栗子:

给出成绩,经过判断,打印出输入的成绩是优秀、良好、中等、及格还是不及格。

let score = 85;

if (score < 0 || score > 100) {
  console.log("成绩不正确");
} else if (score >= 90) {
  console.log("优秀");
} else if (score >= 80) {
  console.log("良好");
} else if (score >= 70) {
  console.log("中等");
} else if (score >= 60) {
  console.log("及格");
} else {
  console.log("不及格");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 上面从上到下依次判断,当满足 score >= 80 后,就不会对后面的条件进行比较了。

执行结果为:

良好

这里需要注意,条件的书写顺序,千万不要下面这样写:

let score = 85;

if (score >= 60) {
  console.log("及格");
} else if (score >= 70) {
  console.log("中等");
} else if (score >= 80) {
  console.log("良好");
} else if (score >= 90) {
  console.log("优秀");
} else if (score < 0 || score > 100) {
  console.log("成绩不正确");
} else {
  console.log("不及格");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 上面只要成绩大于 60 就会进第一个判断,不会走到后面的判断。

if-else语句都是可以相互嵌套的,可以在 if-else 判断中再进行 if-else 判断。

举个栗子:

let x = 10;
let y = 20;

if (x > 5) {
    if (y > 15) {
        console.log("x 大于 5,y 大于 15");
    } else {
        console.log("x 大于 5,y 小于等于 15");
    }
} else {
    if (y > 10) {
        console.log("x 小于等于 5,y 大于 10");
    } else {
        console.log("x 小于等于 5,y 小于等于 10");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

重新修改一下程序,从弹出的对话框中接收输入的值,这里介绍一个 prompt() 函数,可以弹出一个对话框,接收用户的输入。因为输入的值是字符串,需要转换为数值。

let score = prompt("请输入成绩"); // 接收输入的值
if (null == score || score.trim() == "") {
  alert("请输入成绩");
} else {
  score = +score;
  if (isNaN(score) || score > 100 || score < 0) {
    alert("请输入正确的成绩");
  } else {
    if (score >= 90) {
      alert("优秀");
    } else if (score >= 80) {
      alert("良好");
    } else if (score >= 70) {
      alert("中等");
    } else if (score >= 60) {
      alert("及格");
    } else {
      alert("不及格");
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • score.trim() 将会去掉输入内容前后的空格,这里是为了防止输入空格内容;
  • isNaN() 函数可以判断数据是否是 NaN

运行后,页面会弹出一个对话框输入成绩:

# 3 三目运算符

假设现在有两个数字,我们希望获得其中较大的一个,那么可以使用 if-else 语句,例如:

let a = 4;
let b = 5;

let max;
if (a > b)
    max = a;
else
    max = b;

console.log("最大值为:" + max);		// 最大值为:5
1
2
3
4
5
6
7
8
9
10

我们可以使用三目运算符来完成上面的功能,代码会更简洁的写法:

let a = 4;
let b = 5;

let max = a > b ? a : b;
console.log("最大值为:" + max);		// 最大值为:5
1
2
3
4
5

let max = a > b ? a : b; 的意思是:先判断 a > b,如果成立,就返回 a,不成立就返回 b

代码简洁了很多。能用三目运算符的地方都可以使用 if-else 代替。

# 4 switch语句

switch语句也是一种判断语句,通常用于对多个可能的值进行比较。switch语句的语法如下:

switch (value) {
  case value1:
    // value === value1 时执行的代码块
    break;
  case value2:
    // value === value2 时执行的代码块
    break;
  default:
    // value 不等于上面所有的case 时执行的代码块
}
1
2
3
4
5
6
7
8
9
10

注意:switch(value) 中的value,和 case 中的 value 是做全等判断,等于哪个 case,就执行哪个。 default 选择可以省略。

以下是一个示例代码,通过数字判断季节:

let day = 2;

switch (day) {
    case 1:
        console.log("春季");
        break;
    case 2:
        console.log("夏季");
        break;
    case 3:
        console.log("秋季");
        break;
    case 4:
        console.log("冬季");
        break;
    default:
        console.log("错误的季节");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

day 满足哪个 case,就会执行哪个 case 后面的代码。

执行结果:

夏季

注意,每个case后,都需要进行 breakreturn,否则会跳到下一个 case

举个栗子:

let day = 2;
switch (day) {
    case 1:
        console.log("春季");
        break;
    case 2:
        console.log("夏季");
    case 3:
        console.log("秋季");
    case 4:
        console.log("冬季");
        break;
    default:
        console.log("错误的季节");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

因为 case 2 没有 break,所以执行完成,会跳到 case 3 执行,执行完 case 3 没有 breakreturn,会继续跳到 case 4执行,执行完成,case 4break,则跳出。

执行结果:

夏季

秋季

冬季


如果对多个 case 的处理是一样的,就可以将 break 省略。

例如下面的代码中,day 为1,2,3,打印的都是秋季。

let day = 2;
switch (day) {
    case 1:
    case 2:
    case 3:
        console.log("秋季");
    case 4:
        console.log("冬季");
        break;
}
1
2
3
4
5
6
7
8
9
10

# 6.3 循环语句

循环在日常生活中也很常见,例如我们要重复做某件事,就需要用到循环,循环不停的做某件事。

举个栗子,每天向小红送一条玫瑰花,坚持送100天,这个应该怎么实现呢?

# 1 for 循环

for循环是一种常用的循环语句,可以指定循环变量的初始值、终止条件和步长。

for循环的语法如下:

// 对应下面的各个部分
for(①初始化部分; ②循环条件部分; ④迭代部分){
    ③循环体部分;// 执行过程是①-②-③-④-②-③-④-②-③-④......②
// 执行①,然后执行②,判断②是否成立,如果成立,执行③,③执行完成,执行④,④执行完成,再次判断②是否成立,成立继续一轮循环,执行③,②不成立,退出循环。
1
2
3
4
5
6

举个栗子:

for (let i = 1; i <= 5; i++) {
  alert(i);
}
1
2
3

先执行 let i = 1,定义一个变量 i ,然后执行 i <= 5 ,判断 i 是否小于等于5,如果为 true,则执行循环体中的代码 alert(i) ,执行完循环体中的代码,执行 i++ ,然后再次执行 i <= 5 ,判断i 是否小于等于5,直到 i <= 5 不满足,则终止循环。

打印结果:

1

2

3

4

5

再举个例子:

for (let i = 1, j = 10; i <= 5 && j > 0 ; i++, j--) {
    alert(i + j);
}
1
2
3
  • 从上面可以看出,初始化可以定义多个变量,对多个变量进行判断。

当然你也可以将变量定义在外面:

let i = 1;
let j = 10;
// 或者 let i = 1, j = 10;
for (; i <= 5 && j > 0; i++, j--) {
  alert(i + j);
}
1
2
3
4
5
6

在实际的开发中,for 循环是用的最多的。

# 2 while 循环

可以使用 while进行循环操作。

while 循环的语法:

while (condition) {
  // condition为真时执行的代码块
}
1
2
3

代码执行到 while,会判断是否满足条件,如果满足,就会进入while循环,执行满足条件要做的事情,执行完成,会重新判断while后面的条件,如果满足会继续循环,如果不满足就不在进入循环。


回到一开始的提问:每天向小红送一条玫瑰花,坚持送100天,这个应该怎么实现呢?

let i = 1;
while (i <= 100) {
    console.log("第" + i +"天,送你一朵玫瑰花");  // 控制台打印
    i += 1;
}
1
2
3
4
5

首先我们定义了一个变量 i ,用来记录当前是第几天。

我们一开始定义了 i 为 1,执行到 while 循环时,先判断 i 是否小于等于100,如果小于等于100,则进入到循环,打印语句,然后执行 i += 1;i 加1,然后再执行while后面的条件判断,判断 i <= 100,这样一直循环执行,当 i = 101时,刚好已经执行了100次,且不再满足 i < =100,此时终止循环。

注意,一定要对 i 进行累加,如果 i 不进行累加,永远会满足 i <= 100,那么循环将永远不会结束,变成死循环。

执行结果:

第1天,送你一朵玫瑰花

第2天,送你一朵玫瑰花

……

第99天,送你一朵玫瑰花

第100天,送你一朵玫瑰花


我们在计数的时候,i 也可以从0开始,例如:

let i = 0;
while (i < 100) {
    console.log("第" + (i + 1) + "天,送你一朵玫瑰花");  // 控制台打印
    i += 1;
}
1
2
3
4
5

判断的条件的设定是很灵活的,根据实际需求来就可以了,不是一成不变的。


练习:计算从1累加到100的和

let sum = 0; 										// 定义一个变量,存储累加的和
let i = 1; 											// 定义一个变量,标识累加到几了,从1开始累加

while (i <= 100) {
    sum += i; 										// 将和累加
    i += 1; 											// 将i加1,继续累加
}

console.log("1~100的和为:" + sum); 		// 打印最终的结果
1
2
3
4
5
6
7
8
9

执行结果:

1~100的和为:5050

使用 while 循环的地方都可以使用 for 循环代替。

# 3 do-while 循环

do...while 循环与 while 循环的区别在于,do...while 循环先执行一次循环体中的代码,再检查条件是否为真。如果条件为真,则继续执行循环体中的代码。

do...while 循环的语法如下:

do {
  // 循环体中的代码块
} while (condition);
1
2
3

举个栗子,再送一遍花:

let i = 1;

do {
    console.log("第" + i + "天,送你一朵玫瑰花");
    i += 1;
} while (i <= 100);
1
2
3
4
5
6

# 4 死循环

什么是死循环,就是一直循环,不会终止的循环。

举个栗子:

下面的循环中,不执行步长的增长,i 始终为1,则 i <= 5 永远成立,所以循环不会终止。

for (let i = 1; i <= 5;) {
    alert(i);
}
1
2
3

还可以这样写:

let i = 0;
for (;;) {
    alert(i++);
}
1
2
3
4

上面的 for 循环会一直执行,不停的打印 i 的值,i 的值会一直增长。


同样 while 循环也可以定义死循环,非常简单:

while(true) {
  
}
1
2
3

死循环除非是自己需要,需要不停的循环,否则使用的时候要小心一点。

# 5 循环嵌套

循环是可以相互嵌套的,whilefor 循环中可以再次嵌套 whilefor 循环。

例如每天给小红送10朵玫瑰花,坚持100天。

let day = 1; // 记录第几天
while (day <= 100) {			// 外层循环用于循环天数
    for (let roseCount = 1; roseCount <= 10; roseCount++) {			// 内层循环用于循环每一天送的花的朵数

        console.log("第" + day + "天,第" + roseCount + "朵玫瑰花");
    }

    day += 1; // 天数+1
}
1
2
3
4
5
6
7
8
9

forwhiledo-while 循环都可以进行多层嵌套。

一般我们使用 for 循环是最多的,要简洁一些:

for (let day = 1; day <= 100; day++) {			// 外层循环用于循环天数
    for (let roseCount = 1; roseCount <= 10; roseCount++) {			// 内层循环用于循环每一天送的花的朵数
        console.log("第" + day + "天,第" + roseCount + "朵玫瑰花");
    }
}
1
2
3
4
5

# 6 中断循环

我们在前面终止循环,主要是靠不满足条件时自动跳出。这样的话,必须每一次的循环都执行完成,到达条件判断的时候才能跳出。

但是有时候,我们不得不提前退出循环,或者终止当前的循环继续后面的循环,这个时候就需要 breakcontinue 关键字了。


# break 语句

break 关键字用于直接结束当前所在的循环。


举个栗子:

如果一个循环,想在执行第三次的时候跳出:

let i = 0;

while (i < 10) {
    if (i === 3) {
        break;
    }
    console.log("i=" + i);
    i++;
}
1
2
3
4
5
6
7
8
9
  • i = 3 时,跳出了循环。

执行结果:

i=0 i=1 i=2

break只会跳出其所在的循环,举个栗子,循环有嵌套的时候:

for (let i = 0; i < 10; i++) {
    for (let j = 0; i < 10; j++) {
        if (j === 3) {
            break;
        }

        console.log("i=" + i + ", j=" + j);
    }
}
1
2
3
4
5
6
7
8
9
  • 上面的代码,break只能跳出内部的那一个循环。无法直接跳出外部的循环。

如果就是想跳出外部的循环呢?

那就需要给外部的循环添加一个标签,举个栗子:

outer : for (let i = 0; i < 10; i++) {
    for (let j = 0; i < 10; j++) {
        if (j === 3) {
            break outer;
        }

        console.log("i=" + i + ", j=" + j);
    }
}
1
2
3
4
5
6
7
8
9
  • 上面的代码中,给外层循环添加了一个 outer 标签,标签名称是自定义的;在循环内,可以通过 break 标签 来跳出指定的循环。

可以为每个循环添加标签,如果有多层循环,可以通过这样的方式跳出想要跳出的循环。while 循环也可以添加标签。


# continue 语句

continue的作用是中断本次循环,直接进入下一次循环。


举个栗子:

循环5次,在第三次的时候跳过,继续后面的执行:

for (let i = 0; i < 5; i++) {
    if (i === 3) {
        continue;
    }

    console.log("i=" + i);
}
1
2
3
4
5
6
7
  • 在上面 i == 3 的时候,终止了本次循环,直接跳到 i++ ,继续后面的执行。

执行结果:

i=0 i=1 i=2 i=4

同样,continue 和 break 一样,也可以选择终止的是哪一层的循环。

举个栗子:

outer : for (let i = 0; i < 10; i++) {
    for (let j = 0; i < 10; j++) {
        if (j === 3) {
            continue outer;
        }

        console.log("i=" + i + ", j=" + j);
    }
}
1
2
3
4
5
6
7
8
9
  • 在上面的代码中,给外层循环定义了一个标签,每次执行到 j === 3 ,就会终止本次循环,直接跳转到外层循环的 i++,继续后面的循环。

# 练习:求素数

求 100 以内的质数(素数):

// 从 2 开始遍历到 100
for (let i = 2; i <= 100; i++) {
  let isPrime = true;
  // 检查 i 是否为质数
  for (let j = 2; j <= Math.sqrt(i); j++) {
    if (i % j === 0) {  // 能够整除,说明不是素数
      isPrime = false;
      break;  // 直接跳出,判断下一个数是否是素数
    }
  }
  // 如果是质数,则打印该数
  if (isPrime) {
    console.log(i);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 外层 for 循环用来遍历从 2 ~ 100 的数值;
  • 内层 for 循环用来判断每个数值是否能被 2 ~ Math.sqrt(num) 整除,正常情况下求一个数是否是素数,你可能判断该数能否被 2 ~ 该数-1 整除,但是只需要判断 2 ~该数的开方 即可。

# 6.4 调试代码

在实际的开发中,编写的代码逻辑不可能一点不错,有时候代码逻辑有问题,就需要查看原因,通过打印日志的方式,会有一些麻烦。

我们可以在代码执行的时候,添加断点。

举个栗子:

下面的代码在运行的时候,运行到 debugger 会停住,我们可以一句一句运行。

<script>
  debugger;

  for(let i = 0; i < 5; i ++) {
    console.debug();
  }
</script>
1
2
3
4
5
6
7
  • 上面的程序,执行到 debugger 的地方,会停住,等待调试。
  • 程序可以添加多个 debugger 点。

运行的时候,打开浏览器的开发者工具,刷新页面,会进入到调试:

可以一行一行的运行,调试代码,还可以在监视中,添加要监视的变量,查看变量的具体值:

# 6.5 练习

随机生成一个1-100的整数,然后来猜这个数字的大小,通过 prompt() 函数进行输入,猜不对,提示大了还是小了,猜对了,跳出循环。在这里我们需要生成一个随机数,使用 Math.random() 来生成:

// 随机生成一个 1 - 100 的整数
const randomNumber = Math.floor(Math.random() * 100) + 1;

while (true) {
  const userGuess = prompt("请猜1-100的整数");

  // 检查用户输入是否为空或非数字
  if (userGuess === null || isNaN(userGuess)) {
    alert("输入无效,请输入一个数字!");
    continue;
  }

  const guess = parseInt(userGuess);

  if (guess === randomNumber) {
    alert("恭喜你,猜对了!");
    break;
  } else if (guess > randomNumber) {
    alert("猜大了,再试一次");
  } else {
    alert("猜小了,再试一次");
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • Math.random() 是 JS 的内置函数,用于生成一个 >= 0 且 < 1 的随机浮点数,也就是范围 [0, 1) 。那么 Math.random() * 100 的范围是 [0 ~ 100) 的随机小数。
  • Math.floor() 同样是 JavaScript 的内置函数,它的功能是对传入的数字进行向下取整,对 [0 ~ 100) 的随机小数向下取整,得到的数值在 [0, 99] 范围的整数。再加1 就得到 [1,100] 的整数范围。
  • 如果想得到 [0,100] 范围的随机整数,可以使用 Math.floor(Math.random() * 101)