流程控制决定了代码的执行顺序。默认情况下,JavaScript 从上到下逐行执行,但实际业务逻辑需要根据条件选择不同路径,或者重复执行某些操作。本节将逐一介绍分支语句、循环语句和跳转语句,它们是你控制代码流程的基础工具。
8.6.1 分支语句
分支语句让程序根据不同的条件执行不同的代码块。
if...else 语句
最基础的分支结构,有三种形态:
// 形态一:单分支
if (条件) {
// 条件为 true 时执行
}
// 形态二:双分支
if (条件) {
// 条件为 true 时执行
} else {
// 条件为 false 时执行
}
// 形态三:多分支
if (条件1) {
// 条件1为 true 时执行
} else if (条件2) {
// 条件2为 true 时执行
} else {
// 前面所有条件都为 false 时执行
}
实用要点:
- 条件表达式会被隐式转换为布尔值,空字符串、
0、null、undefined、NaN都视为false。 else if并不是独立的语法结构,而是写在同一行避免深层嵌套的简写。等同于:
if (条件1) {
// ...
} else {
if (条件2) {
// ...
} else {
// ...
}
}
- 只有一行代码时,花括号可以省略,但强烈不建议,容易引发歧义和错误。
switch 语句
当需要将一个值与多个可能情况匹配时,switch 比多层 if...else 更清晰:
const fruit = 'apple';
switch (fruit) {
case 'apple':
console.log('苹果');
break; // 跳出 switch,防止穿透
case 'banana':
console.log('香蕉');
break;
default:
console.log('其他水果');
}
注意点:
- 比较使用严格相等(
===),不会发生类型转换。 - 穿透现象:如果没有
break,会继续执行下一个case的代码,直至遇到break或switch结束。某些场景下可以利用穿透(比如多个情况执行相同逻辑),但必须谨慎,避免非预期的行为。 default是可选的,当所有case都不匹配时执行。
8.6.2 循环语句
循环用于重复执行一段代码,直到满足停止条件。
for 循环
最经典的循环,适合事先知道循环次数或能用计数器控制的场景:
for (初始化; 条件; 更新) {
// 循环体
}
// 示例:打印 0 到 4
for (let i = 0; i < 5; i++) {
console.log(i);
}
三个表达式都可以省略,但分号不可省。例如死循环:for (;;)。
while 循环
当条件为 true 时一直执行,适合不确定循环次数、只根据条件决定是否继续的情况:
while (条件) {
// 循环体
}
let count = 0;
while (count < 5) {
console.log(count);
count++;
}
do...while 循环
先执行一次循环体,然后再判断条件。确保循环体至少执行一次:
let i = 0;
do {
console.log(i);
i++;
} while (i < 5);
实际中使用频率不如 while 高,但在需要“先斩后奏”的场景(如用户输入验证,必须先展示提示框)时有用。
for...in 循环
用于遍历对象的可枚举属性(包括原型链上的属性):
const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
console.log(key, obj[key]);
}
重要提醒:
for...in不适合遍历数组,因为它遍历的是索引(作为字符串),且会包含原型上扩展的属性,可能导致意外的结果。- 如果只想获取对象自身的属性,需要在循环内用
hasOwnProperty过滤:
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 处理自身属性
}
}
for...of 循环
ES6 引入的迭代器循环,遍历可迭代对象(如数组、字符串、Map、Set)的值:
const arr = [10, 20, 30];
for (let value of arr) {
console.log(value); // 10, 20, 30
}
const str = 'hello';
for (let char of str) {
console.log(char); // h, e, l, l, o
}
与 for...in 对比:
| 特性 | for...in | for...of |
|------|----------|----------|
| 设计用途 | 遍历对象键名 | 遍历可迭代对象的值 |
| 遍历数组时得到 | 索引(字符串) | 元素值 |
| 支持自定义迭代器 | 否 | 是 |
| 包含原型属性 | 是(需要过滤) | 不会 |
实用场景: 遍历数组优先用 for...of 或 forEach,遍历对象用 Object.keys() / Object.values() 配合普通循环,避免 for...in 的坑。
8.6.3 跳转语句
跳转语句用于改变循环或函数的正常执行流。
break
立即终止当前循环,不再执行后续迭代,常用于提前退出:
for (let i = 0; i < 10; i++) {
if (i === 5) break;
console.log(i); // 输出 0 到 4
}
在嵌套循环中,break 只退出最内层循环。如果想要跳出外层循环,可以使用标签(见下文)。
continue
跳过当前迭代的剩余代码,直接进入下一次循环的条件判断:
for (let i = 0; i < 5; i++) {
if (i === 2) continue;
console.log(i); // 输出 0, 1, 3, 4,跳过了 2
}
return
用于函数体内,终止函数执行并返回一个值(可省略,返回 undefined)。当 return 出现在循环中时,不仅终止循环,还直接结束整个函数:
function findFirstEven(arr) {
for (let num of arr) {
if (num % 2 === 0) return num;
}
return null; // 没找到则返回 null
}
标签语句
JavaScript 允许给语句加标签,配合 break 和 continue 跳转到指定的外层循环或代码块:
outerLoop: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) break outerLoop; // 跳出外层循环
console.log(i, j);
}
}
// 输出: 0 0, 0 1, 0 2, 1 0,然后跳出
标签在实际工作中不常用,但在处理多重循环且需要一次性跳出的场景下非常有效,相比设置多个标志变量更简洁。
8.6.4 实用建议
- 优先使用可读性高的写法
for...of 配合迭代器让循环意图更清晰;能用 forEach 表达的就不要自己写 for 循环,除非需要提前退出。
- 避免死循环
确保循环的终止条件最终能达到,尤其在 while 和 do...while 中。开发时可加入最大迭代次数计数器,防止浏览器页面卡死。
switch不要漏掉break
大部分情况下每个 case 后都要跟 break,即使放在 default 后也可以习惯性加一个(虽然不需要)。
- 注意数组遍历的坑
for...in遍历数组会得到字符串索引且可能包含原型属性,不要用。for...of遍历数组得到的是值,无法直接获取索引,如需索引可用arr.entries()。- 稀疏数组的遍历行为有差异,
forEach和for...of会跳过空元素,而普通for循环会访问undefined。
掌握了这些流程控制语句,你就能够编写出灵活且健壮的程序逻辑。它们是构建更复杂算法和业务逻辑的基础积木。