JavaScript|第6章:流程控制语句
流程控制语句
- JavaScript|第6章:流程控制语句
-
- 引言
- [6.1 条件语句:if...else 与 switch](#6.1 条件语句:if...else 与 switch)
-
- [6.1.1 if...else 语句](#6.1.1 if...else 语句)
- [6.1.2 三元运算符:简洁的条件表达式](#6.1.2 三元运算符:简洁的条件表达式)
- [6.1.3 switch 语句](#6.1.3 switch 语句)
- [6.1.4 if...else vs switch:如何选择?](#6.1.4 if...else vs switch:如何选择?)
- [6.2 循环语句:for、while、do...while](#6.2 循环语句:for、while、do...while)
-
- [6.2.1 for 循环](#6.2.1 for 循环)
- [6.2.2 while 循环](#6.2.2 while 循环)
- [6.2.3 do...while 循环](#6.2.3 do...while 循环)
- [6.2.4 三种循环对比](#6.2.4 三种循环对比)
- [6.2.5 for...of 与 for...in 的区别](#6.2.5 for...of 与 for...in 的区别)
- [6.3 break、continue 与标签语句](#6.3 break、continue 与标签语句)
-
- [6.3.1 break:跳出循环](#6.3.1 break:跳出循环)
- [6.3.2 continue:跳过本次迭代](#6.3.2 continue:跳过本次迭代)
- [6.3.3 标签语句(Label Statement)](#6.3.3 标签语句(Label Statement))
- [6.4 异常处理:try...catch...finally](#6.4 异常处理:try...catch...finally)
-
- [6.4.1 基本语法](#6.4.1 基本语法)
- [6.4.2 throw 语句](#6.4.2 throw 语句)
- [6.4.3 Error 对象](#6.4.3 Error 对象)
- [6.4.4 自定义 Error 类](#6.4.4 自定义 Error 类)
- [6.4.5 finally 的执行时机](#6.4.5 finally 的执行时机)
- [6.4.6 try...catch 的常见应用场景](#6.4.6 try...catch 的常见应用场景)
- [6.5 实战:猜数字游戏](#6.5 实战:猜数字游戏)
-
- [6.5.1 需求分析](#6.5.1 需求分析)
- [6.5.2 命令行版本(Node.js)](#6.5.2 命令行版本(Node.js))
- [6.5.3 代码结构解析](#6.5.3 代码结构解析)
- [6.5.4 进阶版本:二分查找策略](#6.5.4 进阶版本:二分查找策略)
- [6.6 常见面试题精讲](#6.6 常见面试题精讲)
-
- [面试题 1:for...in 和 for...of 的区别是什么?](#面试题 1:for...in 和 for...of 的区别是什么?)
- [面试题 2:以下代码输出什么?为什么?](#面试题 2:以下代码输出什么?为什么?)
- [面试题 3:try...catch 能捕获所有错误吗?](#面试题 3:try...catch 能捕获所有错误吗?)
- [面试题 4:switch 使用 == 还是 === 比较?](#面试题 4:switch 使用 == 还是 === 比较?)
- 本章小结
上篇回顾 :第5章:运算符与表达式
下篇预告 :第7章:函数基础
引言
在前面的章节中,我们学习了变量、数据类型、类型转换和运算符。但到目前为止,我们的代码都是从上到下逐行执行的------这被称为"顺序结构"。
现实世界的问题远比顺序执行复杂。你需要根据用户的年龄判断是否成年,需要遍历一个数组找到所有偶数,需要在输入非法数据时优雅地处理错误......这些场景都需要流程控制。
#mermaid-svg-MmCy9mtqGym2pLWY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MmCy9mtqGym2pLWY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MmCy9mtqGym2pLWY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MmCy9mtqGym2pLWY .error-icon{fill:#552222;}#mermaid-svg-MmCy9mtqGym2pLWY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MmCy9mtqGym2pLWY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MmCy9mtqGym2pLWY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MmCy9mtqGym2pLWY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MmCy9mtqGym2pLWY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MmCy9mtqGym2pLWY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MmCy9mtqGym2pLWY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MmCy9mtqGym2pLWY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MmCy9mtqGym2pLWY .marker.cross{stroke:#333333;}#mermaid-svg-MmCy9mtqGym2pLWY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MmCy9mtqGym2pLWY p{margin:0;}#mermaid-svg-MmCy9mtqGym2pLWY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MmCy9mtqGym2pLWY .cluster-label text{fill:#333;}#mermaid-svg-MmCy9mtqGym2pLWY .cluster-label span{color:#333;}#mermaid-svg-MmCy9mtqGym2pLWY .cluster-label span p{background-color:transparent;}#mermaid-svg-MmCy9mtqGym2pLWY .label text,#mermaid-svg-MmCy9mtqGym2pLWY span{fill:#333;color:#333;}#mermaid-svg-MmCy9mtqGym2pLWY .node rect,#mermaid-svg-MmCy9mtqGym2pLWY .node circle,#mermaid-svg-MmCy9mtqGym2pLWY .node ellipse,#mermaid-svg-MmCy9mtqGym2pLWY .node polygon,#mermaid-svg-MmCy9mtqGym2pLWY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MmCy9mtqGym2pLWY .rough-node .label text,#mermaid-svg-MmCy9mtqGym2pLWY .node .label text,#mermaid-svg-MmCy9mtqGym2pLWY .image-shape .label,#mermaid-svg-MmCy9mtqGym2pLWY .icon-shape .label{text-anchor:middle;}#mermaid-svg-MmCy9mtqGym2pLWY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MmCy9mtqGym2pLWY .rough-node .label,#mermaid-svg-MmCy9mtqGym2pLWY .node .label,#mermaid-svg-MmCy9mtqGym2pLWY .image-shape .label,#mermaid-svg-MmCy9mtqGym2pLWY .icon-shape .label{text-align:center;}#mermaid-svg-MmCy9mtqGym2pLWY .node.clickable{cursor:pointer;}#mermaid-svg-MmCy9mtqGym2pLWY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MmCy9mtqGym2pLWY .arrowheadPath{fill:#333333;}#mermaid-svg-MmCy9mtqGym2pLWY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MmCy9mtqGym2pLWY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MmCy9mtqGym2pLWY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MmCy9mtqGym2pLWY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MmCy9mtqGym2pLWY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MmCy9mtqGym2pLWY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MmCy9mtqGym2pLWY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MmCy9mtqGym2pLWY .cluster text{fill:#333;}#mermaid-svg-MmCy9mtqGym2pLWY .cluster span{color:#333;}#mermaid-svg-MmCy9mtqGym2pLWY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MmCy9mtqGym2pLWY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MmCy9mtqGym2pLWY rect.text{fill:none;stroke-width:0;}#mermaid-svg-MmCy9mtqGym2pLWY .icon-shape,#mermaid-svg-MmCy9mtqGym2pLWY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MmCy9mtqGym2pLWY .icon-shape p,#mermaid-svg-MmCy9mtqGym2pLWY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MmCy9mtqGym2pLWY .icon-shape .label rect,#mermaid-svg-MmCy9mtqGym2pLWY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MmCy9mtqGym2pLWY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MmCy9mtqGym2pLWY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MmCy9mtqGym2pLWY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 程序三大结构
顺序结构
分支结构
循环结构
if...else
switch
for / while
do...while
本章目标:
- 掌握
if...else、switch条件语句的用法与选择策略 - 熟练使用
for、while、do...while三种循环 - 理解
break、continue与标签语句在嵌套循环中的作用 - 掌握
try...catch...finally异常处理机制 - 通过猜数字游戏综合运用所有流程控制语句
6.1 条件语句:if...else 与 switch
条件语句让程序能够根据不同条件执行不同的代码分支。JavaScript 提供了两种条件语句:if...else 和 switch。
6.1.1 if...else 语句
if...else 是最基本、最常用的条件语句。
javascript
const age = 18;
if (age >= 18) {
console.log('成年人');
} else {
console.log('未成年人');
}
多条件判断:else if:
javascript
const score = 85;
if (score >= 90) {
console.log('优秀');
} else if (score >= 80) {
console.log('良好');
} else if (score >= 60) {
console.log('及格');
} else {
console.log('不及格');
}
条件表达式中的隐式转换:
if 语句会将条件表达式转换为布尔值。在第4章中我们已经学过 falsy 值,这里再强调一下常见的陷阱:
javascript
// ✅ 正确:检查数组是否为空
const arr = [];
if (arr.length === 0) {
console.log('空数组');
}
// ❌ 错误:空数组是 truthy
if (arr) {
console.log('这行会执行,但 arr 可能是空数组');
}
// ✅ 正确:检查对象属性
const user = { name: 'Alice' };
if (user.age !== undefined) {
console.log('有 age 属性');
}
// 更简洁的写法
if ('age' in user) {
console.log('有 age 属性');
}
最佳实践 :条件表达式尽量使用显式比较(
===、!==、>、<),避免依赖隐式转换,代码可读性和可维护性更高。
6.1.2 三元运算符:简洁的条件表达式
三元运算符 ? : 是 if...else 的表达式形式,适合简单的条件赋值。
javascript
const age = 20;
const status = age >= 18 ? '成年人' : '未成年人';
console.log(status); // "成年人"
链式三元运算符(不推荐过度使用):
javascript
const score = 85;
const grade = score >= 90 ? 'A'
: score >= 80 ? 'B'
: score >= 60 ? 'C'
: 'D';
console.log(grade); // "B"
使用建议 :三元运算符适合简单的条件赋值和函数参数。如果逻辑复杂,应使用
if...else,可读性更重要。
6.1.3 switch 语句
当需要对同一个值 进行多个等值判断时,switch 比一连串的 if...else if 更清晰。
javascript
const day = new Date().getDay(); // 0-6,周日到周六
switch (day) {
case 0:
console.log('周日');
break;
case 1:
console.log('周一');
break;
case 2:
console.log('周二');
break;
case 3:
console.log('周三');
break;
case 4:
console.log('周四');
break;
case 5:
console.log('周五');
break;
case 6:
console.log('周六');
break;
default:
console.log('未知');
}
switch 的穿透(Fall-through)特性:
如果省略 break,代码会继续执行下一个 case ,这就是穿透。有时候可以利用这个特性,但更多时候忘记写 break 会导致 Bug。
javascript
// 利用穿透:合并相同逻辑
const month = 3;
let season;
switch (month) {
case 12:
case 1:
case 2:
season = '冬季';
break;
case 3:
case 4:
case 5:
season = '春季';
break;
case 6:
case 7:
case 8:
season = '夏季';
break;
case 9:
case 10:
case 11:
season = '秋季';
break;
default:
season = '未知';
}
console.log(season); // "春季"
注意 :
switch使用===(严格相等)进行比较,不会发生类型转换。
javascript
const value = '1';
switch (value) {
case 1:
console.log('数字 1'); // 不会执行
break;
case '1':
console.log('字符串 "1"'); // ✅ 执行这个
break;
}
6.1.4 if...else vs switch:如何选择?
| 场景 | 推荐语句 | 原因 |
|---|---|---|
范围判断(如 x > 10) |
if...else |
switch 只能做等值比较 |
| 对同一值的多个等值分支 | switch |
结构清晰,性能好 |
| 2-3 个简单分支 | if...else 或三元 |
代码量少,无需 switch |
| 需要复杂条件组合 | if...else |
switch 不支持复杂表达式 |
| 分支超过 5 个且是等值判断 | switch 或对象映射 |
可读性优于 if...else 链 |
对象映射替代方案(现代 JavaScript 推荐):
javascript
// switch 写法
switch (status) {
case 'pending': return '等待中';
case 'success': return '成功';
case 'error': return '失败';
default: return '未知';
}
// 对象映射写法(更简洁)
const statusMap = {
pending: '等待中',
success: '成功',
error: '失败'
};
return statusMap[status] || '未知';
6.2 循环语句:for、while、do...while
循环语句用于重复执行一段代码。JavaScript 提供了三种基本循环和两种遍历循环。
6.2.1 for 循环
for 循环是最常用的循环,适合已知循环次数的场景。
javascript
// 基本语法
for (初始化; 条件; 更新) {
// 循环体
}
// 打印 0 到 4
for (let i = 0; i < 5; i++) {
console.log(i);
}
// 输出:0 1 2 3 4
遍历数组:
javascript
const fruits = ['苹果', '香蕉', '橙子'];
for (let i = 0; i < fruits.length; i++) {
console.log(`${i + 1}. ${fruits[i]}`);
}
// 输出:
// 1. 苹果
// 2. 香蕉
// 3. 橙子
for...of 循环(ES6,遍历可迭代对象):
javascript
const fruits = ['苹果', '香蕉', '橙子'];
for (const fruit of fruits) {
console.log(fruit);
}
// 输出:苹果、香蕉、橙子
javascript
// for...of 也可以遍历字符串
for (const char of 'Hello') {
console.log(char);
}
// 输出:H e l l o
for...in 循环(遍历对象的可枚举属性):
javascript
const user = { name: 'Alice', age: 25, city: '北京' };
for (const key in user) {
console.log(`${key}: ${user[key]}`);
}
// 输出:
// name: Alice
// age: 25
// city: 北京
注意 :
for...in会遍历原型链上的可枚举属性,通常配合hasOwnProperty()使用,或者用Object.keys()代替。对数组使用for...in是不推荐的做法。
javascript
// ❌ 不推荐:对数组使用 for...in
const arr = [10, 20, 30];
for (const i in arr) {
console.log(i); // "0" "1" "2"(字符串类型!)
console.log(arr[i]); // 10 20 30
}
// ✅ 推荐:对数组使用 for...of
for (const value of arr) {
console.log(value); // 10 20 30
}
6.2.2 while 循环
while 循环适合不确定循环次数的场景,先判断条件,再执行循环体。
javascript
let count = 0;
while (count < 5) {
console.log(count);
count++;
}
// 输出:0 1 2 3 4
经典应用:辗转相除法求最大公约数:
javascript
function gcd(a, b) {
while (b !== 0) {
const temp = b;
b = a % b;
a = temp;
}
return a;
}
console.log(gcd(48, 18)); // 6
console.log(gcd(100, 75)); // 25
6.2.3 do...while 循环
do...while 与 while 类似,但先执行一次循环体,再判断条件。保证循环体至少执行一次。
javascript
let num;
do {
num = Math.floor(Math.random() * 10);
console.log(`随机数:${num}`);
} while (num !== 7);
console.log('终于摇到 7 了!');
经典应用:用户输入验证:
javascript
// 模拟 Node.js 命令行交互
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let input;
function askInput() {
rl.question('请输入一个 1-10 的数字:', (answer) => {
input = parseInt(answer);
if (input >= 1 && input <= 10) {
console.log(`你输入了:${input}`);
rl.close();
} else {
console.log('输入无效,请重新输入');
askInput();
}
});
}
askInput();
6.2.4 三种循环对比
| 特性 | for | while | do...while |
|---|---|---|---|
| 适用场景 | 已知循环次数 | 不确定次数,可能不执行 | 至少执行一次 |
| 条件检查时机 | 每次循环前 | 每次循环前 | 每次循环后 |
| 最少执行次数 | 0 次 | 0 次 | 1 次 |
| 循环变量管理 | 内置(初始化/更新) | 需手动管理 | 需手动管理 |
| 典型用途 | 遍历数组、计数 | 搜索、收敛算法 | 输入验证、菜单 |
6.2.5 for...of 与 for...in 的区别
这是面试高频考点,必须清晰理解。
#mermaid-svg-O4gHnBegCe2JtvFo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-O4gHnBegCe2JtvFo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-O4gHnBegCe2JtvFo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-O4gHnBegCe2JtvFo .error-icon{fill:#552222;}#mermaid-svg-O4gHnBegCe2JtvFo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-O4gHnBegCe2JtvFo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-O4gHnBegCe2JtvFo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-O4gHnBegCe2JtvFo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-O4gHnBegCe2JtvFo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-O4gHnBegCe2JtvFo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-O4gHnBegCe2JtvFo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-O4gHnBegCe2JtvFo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-O4gHnBegCe2JtvFo .marker.cross{stroke:#333333;}#mermaid-svg-O4gHnBegCe2JtvFo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-O4gHnBegCe2JtvFo p{margin:0;}#mermaid-svg-O4gHnBegCe2JtvFo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-O4gHnBegCe2JtvFo .cluster-label text{fill:#333;}#mermaid-svg-O4gHnBegCe2JtvFo .cluster-label span{color:#333;}#mermaid-svg-O4gHnBegCe2JtvFo .cluster-label span p{background-color:transparent;}#mermaid-svg-O4gHnBegCe2JtvFo .label text,#mermaid-svg-O4gHnBegCe2JtvFo span{fill:#333;color:#333;}#mermaid-svg-O4gHnBegCe2JtvFo .node rect,#mermaid-svg-O4gHnBegCe2JtvFo .node circle,#mermaid-svg-O4gHnBegCe2JtvFo .node ellipse,#mermaid-svg-O4gHnBegCe2JtvFo .node polygon,#mermaid-svg-O4gHnBegCe2JtvFo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-O4gHnBegCe2JtvFo .rough-node .label text,#mermaid-svg-O4gHnBegCe2JtvFo .node .label text,#mermaid-svg-O4gHnBegCe2JtvFo .image-shape .label,#mermaid-svg-O4gHnBegCe2JtvFo .icon-shape .label{text-anchor:middle;}#mermaid-svg-O4gHnBegCe2JtvFo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-O4gHnBegCe2JtvFo .rough-node .label,#mermaid-svg-O4gHnBegCe2JtvFo .node .label,#mermaid-svg-O4gHnBegCe2JtvFo .image-shape .label,#mermaid-svg-O4gHnBegCe2JtvFo .icon-shape .label{text-align:center;}#mermaid-svg-O4gHnBegCe2JtvFo .node.clickable{cursor:pointer;}#mermaid-svg-O4gHnBegCe2JtvFo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-O4gHnBegCe2JtvFo .arrowheadPath{fill:#333333;}#mermaid-svg-O4gHnBegCe2JtvFo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-O4gHnBegCe2JtvFo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-O4gHnBegCe2JtvFo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O4gHnBegCe2JtvFo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-O4gHnBegCe2JtvFo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O4gHnBegCe2JtvFo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-O4gHnBegCe2JtvFo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-O4gHnBegCe2JtvFo .cluster text{fill:#333;}#mermaid-svg-O4gHnBegCe2JtvFo .cluster span{color:#333;}#mermaid-svg-O4gHnBegCe2JtvFo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-O4gHnBegCe2JtvFo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-O4gHnBegCe2JtvFo rect.text{fill:none;stroke-width:0;}#mermaid-svg-O4gHnBegCe2JtvFo .icon-shape,#mermaid-svg-O4gHnBegCe2JtvFo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-O4gHnBegCe2JtvFo .icon-shape p,#mermaid-svg-O4gHnBegCe2JtvFo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-O4gHnBegCe2JtvFo .icon-shape .label rect,#mermaid-svg-O4gHnBegCe2JtvFo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-O4gHnBegCe2JtvFo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-O4gHnBegCe2JtvFo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-O4gHnBegCe2JtvFo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 循环遍历
for...of
for...in
遍历值(Values)
遍历键(Keys)
适用于可迭代对象
适用于对象属性
数组、字符串、Map、Set
普通对象
| 特性 | for...of | for...in |
|---|---|---|
| 遍历内容 | 值(value) | 键(key) |
| 适用对象 | 可迭代对象(有 Symbol.iterator) |
任意对象 |
| 原型链属性 | 不会遍历 | 会遍历(需用 hasOwnProperty 过滤) |
| 遍历顺序 | 按迭代器定义的顺序 | 不保证顺序 |
| 推荐用于数组 | ✅ 推荐 | ❌ 不推荐 |
javascript
const arr = ['a', 'b', 'c'];
// for...of → 遍历值
for (const item of arr) {
console.log(item); // "a" "b" "c"
}
// for...in → 遍历键(索引)
for (const index in arr) {
console.log(index); // "0" "1" "2"(注意是字符串)
}
// entries() → 同时获取键和值
for (const [index, value] of arr.entries()) {
console.log(`${index}: ${value}`);
}
6.3 break、continue 与标签语句
break 和 continue 用于在循环内部控制执行流程。配合标签语句,可以在嵌套循环中实现更精确的跳转。
6.3.1 break:跳出循环
break 立即终止当前循环或 switch 语句,跳到循环之后的代码。
javascript
// 在数组中查找第一个偶数
const numbers = [1, 3, 5, 4, 7, 8];
let firstEven = null;
for (const num of numbers) {
if (num % 2 === 0) {
firstEven = num;
break; // 找到后立即停止循环
}
}
console.log(firstEven); // 4
6.3.2 continue:跳过本次迭代
continue 跳过当前迭代,直接进入下一次循环。
javascript
// 打印 1-10 中的奇数
for (let i = 1; i <= 10; i++) {
if (i % 2 === 0) {
continue; // 跳过偶数
}
console.log(i);
}
// 输出:1 3 5 7 9
6.3.3 标签语句(Label Statement)
在嵌套循环中,break 和 continue 只能作用于最内层循环。使用标签语句可以指定要跳出或跳过哪一层循环。
javascript
// 标签语句语法:labelName: statement
// 在二维数组中查找目标值
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
const target = 5;
let found = false;
outer: for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] === target) {
found = true;
console.log(`找到 ${target},位置:[${i}][${j}]`);
break outer; // 直接跳出外层循环
}
}
}
console.log(found); // true
continue 配合标签:
javascript
// 打印九九乘法表,跳过偶数行
outer: for (let i = 1; i <= 9; i++) {
if (i % 2 === 0) {
continue outer; // 跳过整个偶数行
}
let row = '';
for (let j = 1; j <= i; j++) {
row += `${j}×${i}=${i * j}\t`;
}
console.log(row);
}
// 只打印第 1、3、5、7、9 行
使用建议 :标签语句在深层嵌套循环中有用,但如果嵌套超过 3 层,建议将内层逻辑抽取为独立函数,用
return替代break label,代码可读性更好。
6.4 异常处理:try...catch...finally
程序运行时难免会遇到错误。JavaScript 提供了 try...catch...finally 语句来捕获和处理异常,避免程序因未处理的错误而崩溃。
6.4.1 基本语法
javascript
try {
// 可能抛出异常的代码
const result = JSON.parse('这不是合法的 JSON');
} catch (error) {
// 处理异常
console.log('解析失败:', error.message);
} finally {
// 无论是否异常都会执行
console.log('清理工作完成');
}
// 输出:
// 解析失败: Unexpected token '这', "这不是合法的 JSON" is not valid JSON
// 清理工作完成
三个部分的作用:
| 部分 | 是否必须 | 执行时机 | 典型用途 |
|---|---|---|---|
try |
✅ 必须 | 始终执行 | 放置可能出错的代码 |
catch |
至少一个 | try 中抛出异常时 |
处理错误、记录日志 |
finally |
可选 | 无论是否异常 | 资源清理、关闭连接 |
6.4.2 throw 语句
使用 throw 可以主动抛出异常。
javascript
function divide(a, b) {
if (b === 0) {
throw new Error('除数不能为零');
}
return a / b;
}
try {
console.log(divide(10, 0));
} catch (error) {
console.log('错误:', error.message); // "错误: 除数不能为零"
}
throw 可以抛出任意值 (但推荐抛出 Error 对象):
javascript
// ✅ 推荐:抛出 Error 对象(包含 stack trace)
throw new Error('Something went wrong');
// ✅ 可以抛出自定义对象
throw { code: 404, message: 'Not Found' };
// ❌ 不推荐:抛出原始值(没有堆栈信息)
throw 'something went wrong';
6.4.3 Error 对象
JavaScript 内置了多种错误类型:
javascript
// 常见错误类型
try { undefinedVariable; } catch (e) { console.log(e.name); } // ReferenceError
try { null.property; } catch (e) { console.log(e.name); } // TypeError
try { eval('%%%'); } catch (e) { console.log(e.name); } // SyntaxError
// Error 对象的关键属性
const err = new Error('出错了');
console.log(err.name); // "Error"
console.log(err.message); // "出错了"
console.log(err.stack); // 完整的调用栈信息
常见错误类型一览:
| 错误类型 | 触发场景 | 示例 |
|---|---|---|
ReferenceError |
引用不存在的变量 | console.log(x) |
TypeError |
值的类型不正确 | null.foo、1() |
SyntaxError |
代码语法错误 | eval('{') |
RangeError |
值超出范围 | new Array(-1) |
URIError |
URI 处理函数参数错误 | decodeURIComponent('%') |
6.4.4 自定义 Error 类
在实际项目中,自定义错误类型可以让错误处理更精确。
javascript
class ValidationError extends Error {
constructor(field, message) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
class NotFoundError extends Error {
constructor(resource) {
super(`${resource} 不存在`);
this.name = 'NotFoundError';
this.resource = resource;
}
}
// 使用
function findUser(id) {
if (id <= 0) {
throw new ValidationError('id', 'ID 必须为正整数');
}
// 模拟查找
throw new NotFoundError('User');
}
try {
findUser(-1);
} catch (error) {
if (error instanceof ValidationError) {
console.log(`验证失败 [${error.field}]: ${error.message}`);
} else if (error instanceof NotFoundError) {
console.log(`未找到: ${error.resource}`);
} else {
throw error; // 未知错误,继续向上抛出
}
}
// 输出:验证失败 [id]: ID 必须为正整数
6.4.5 finally 的执行时机
finally 块无论 try 中是否发生异常都会执行 ,甚至在 try 或 catch 中有 return 语句时也不例外。
javascript
function test() {
try {
console.log('try 执行');
return 'from try';
} catch (error) {
return 'from catch';
} finally {
console.log('finally 执行'); // 即使 try 中有 return,也会执行
}
}
console.log(test());
// 输出:
// try 执行
// finally 执行
// from try
关键点 :
finally中的return会覆盖try和catch中的return。因此不建议在finally中使用return。
javascript
function badExample() {
try {
return 'try 的返回值';
} finally {
return 'finally 的返回值'; // 覆盖了 try 的返回值
}
}
console.log(badExample()); // "finally 的返回值"
6.4.6 try...catch 的常见应用场景
场景1:安全解析 JSON
javascript
function safeParse(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.warn('JSON 解析失败:', error.message);
return null;
}
}
const data = safeParse('{"name": "Alice"}'); // { name: 'Alice' }
const bad = safeParse('invalid'); // null
场景2:带重试的网络请求
javascript
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.warn(`第 ${attempt} 次请求失败: ${error.message}`);
if (attempt === maxRetries) {
throw new Error(`请求失败,已重试 ${maxRetries} 次`);
}
// 指数退避等待
await new Promise(resolve =>
setTimeout(resolve, 1000 * Math.pow(2, attempt))
);
}
}
}
6.5 实战:猜数字游戏
让我们综合运用本章所有知识,实现一个完整的猜数字游戏。
6.5.1 需求分析
- 程序随机生成一个 1-100 的整数
- 玩家每次输入一个数字进行猜测
- 程序提示"太大了"或"太小了"
- 记录猜测次数,猜中后显示
- 限制最多猜 10 次
6.5.2 命令行版本(Node.js)
javascript
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function ask(question) {
return new Promise((resolve) => {
rl.question(question, (answer) => {
resolve(answer);
});
});
}
async function guessingGame() {
const secret = Math.floor(Math.random() * 100) + 1;
const maxAttempts = 10;
let attempts = 0;
console.log('🎮 猜数字游戏!');
console.log(`我想了一个 1-100 之间的整数,你有 ${maxAttempts} 次机会。`);
console.log('---');
while (attempts < maxAttempts) {
attempts++;
const input = await ask(`第 ${attempts} 次猜测(1-100):`);
const guess = parseInt(input);
// 输入验证
if (isNaN(guess) || guess < 1 || guess > 100) {
console.log('⚠️ 请输入 1-100 之间的整数!');
attempts--; // 无效输入不计次数
continue;
}
// 判断逻辑
if (guess === secret) {
console.log(`🎉 恭喜你!答案就是 ${secret},你用了 ${attempts} 次猜中!`);
break;
} else if (guess < secret) {
console.log('📈 太小了,再大一点!');
} else {
console.log('📉 太大了,再小一点!');
}
// 提示剩余次数
const remaining = maxAttempts - attempts;
if (remaining > 0) {
console.log(`(剩余 ${remaining} 次机会)`);
}
}
// 超过最大次数
if (attempts >= maxAttempts && guess !== secret) {
console.log(`😢 次数用完了!答案是 ${secret}`);
}
rl.close();
}
guessingGame();
6.5.3 代码结构解析
#mermaid-svg-bEvtdbBXrhlVdajJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bEvtdbBXrhlVdajJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bEvtdbBXrhlVdajJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bEvtdbBXrhlVdajJ .error-icon{fill:#552222;}#mermaid-svg-bEvtdbBXrhlVdajJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bEvtdbBXrhlVdajJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bEvtdbBXrhlVdajJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bEvtdbBXrhlVdajJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bEvtdbBXrhlVdajJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bEvtdbBXrhlVdajJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bEvtdbBXrhlVdajJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bEvtdbBXrhlVdajJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bEvtdbBXrhlVdajJ .marker.cross{stroke:#333333;}#mermaid-svg-bEvtdbBXrhlVdajJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bEvtdbBXrhlVdajJ p{margin:0;}#mermaid-svg-bEvtdbBXrhlVdajJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bEvtdbBXrhlVdajJ .cluster-label text{fill:#333;}#mermaid-svg-bEvtdbBXrhlVdajJ .cluster-label span{color:#333;}#mermaid-svg-bEvtdbBXrhlVdajJ .cluster-label span p{background-color:transparent;}#mermaid-svg-bEvtdbBXrhlVdajJ .label text,#mermaid-svg-bEvtdbBXrhlVdajJ span{fill:#333;color:#333;}#mermaid-svg-bEvtdbBXrhlVdajJ .node rect,#mermaid-svg-bEvtdbBXrhlVdajJ .node circle,#mermaid-svg-bEvtdbBXrhlVdajJ .node ellipse,#mermaid-svg-bEvtdbBXrhlVdajJ .node polygon,#mermaid-svg-bEvtdbBXrhlVdajJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bEvtdbBXrhlVdajJ .rough-node .label text,#mermaid-svg-bEvtdbBXrhlVdajJ .node .label text,#mermaid-svg-bEvtdbBXrhlVdajJ .image-shape .label,#mermaid-svg-bEvtdbBXrhlVdajJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-bEvtdbBXrhlVdajJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bEvtdbBXrhlVdajJ .rough-node .label,#mermaid-svg-bEvtdbBXrhlVdajJ .node .label,#mermaid-svg-bEvtdbBXrhlVdajJ .image-shape .label,#mermaid-svg-bEvtdbBXrhlVdajJ .icon-shape .label{text-align:center;}#mermaid-svg-bEvtdbBXrhlVdajJ .node.clickable{cursor:pointer;}#mermaid-svg-bEvtdbBXrhlVdajJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bEvtdbBXrhlVdajJ .arrowheadPath{fill:#333333;}#mermaid-svg-bEvtdbBXrhlVdajJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bEvtdbBXrhlVdajJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bEvtdbBXrhlVdajJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bEvtdbBXrhlVdajJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bEvtdbBXrhlVdajJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bEvtdbBXrhlVdajJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bEvtdbBXrhlVdajJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bEvtdbBXrhlVdajJ .cluster text{fill:#333;}#mermaid-svg-bEvtdbBXrhlVdajJ .cluster span{color:#333;}#mermaid-svg-bEvtdbBXrhlVdajJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bEvtdbBXrhlVdajJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bEvtdbBXrhlVdajJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-bEvtdbBXrhlVdajJ .icon-shape,#mermaid-svg-bEvtdbBXrhlVdajJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bEvtdbBXrhlVdajJ .icon-shape p,#mermaid-svg-bEvtdbBXrhlVdajJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bEvtdbBXrhlVdajJ .icon-shape .label rect,#mermaid-svg-bEvtdbBXrhlVdajJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bEvtdbBXrhlVdajJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bEvtdbBXrhlVdajJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bEvtdbBXrhlVdajJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
太小
太大
是
否
生成随机数 1-100
进入循环
读取用户输入
输入合法?
提示重新输入
猜中了?
恭喜 + 显示次数
太大还是太小?
提示太小
提示太大
还有剩余次数?
游戏结束 + 公布答案
这段代码用到了:
while循环:控制游戏主循环if...else if...else:判断猜测结果break:猜中后跳出循环continue:无效输入时跳过本次计数parseInt()与isNaN():输入验证与类型转换
6.5.4 进阶版本:二分查找策略
聪明的玩家会使用二分查找 策略,每次猜中间值,最多 7 次就能猜中 1-100 的任意数字(因为 2 7 = 128 > 100 2^7 = 128 > 100 27=128>100)。
javascript
// 模拟二分查找策略的自动猜测
function autoGuess(min = 1, max = 100) {
const secret = Math.floor(Math.random() * max) + min;
let low = min;
let high = max;
let attempts = 0;
console.log(`秘密数字已生成(${min}-${max})`);
while (low <= high) {
attempts++;
const guess = Math.floor((low + high) / 2);
if (guess === secret) {
console.log(`第 ${attempts} 次:猜 ${guess} → 🎉 猜中了!`);
break;
} else if (guess < secret) {
console.log(`第 ${attempts} 次:猜 ${guess} → 太小`);
low = guess + 1;
} else {
console.log(`第 ${attempts} 次:猜 ${guess} → 太大`);
high = guess - 1;
}
}
console.log(`共用了 ${attempts} 次(最多 ${Math.ceil(Math.log2(max - min + 1))} 次)`);
}
autoGuess(); // 每次运行都不会超过 7 次
算法启示 :二分查找的时间复杂度为 O ( log n ) O(\log n) O(logn),对于 1-100 的范围,最多只需要 7 次比较。这体现了算法选择对效率的巨大影响。
6.6 常见面试题精讲
面试题 1:for...in 和 for...of 的区别是什么?
参考答案:
for...in遍历对象的可枚举属性名(键),会遍历原型链上的属性,适合遍历普通对象。for...of遍历可迭代对象的值 ,不会遍历原型链属性,适用于数组、字符串、Map、Set 等实现了Symbol.iterator接口的对象。- 对数组使用
for...in是不推荐的,因为它返回的是字符串类型的索引,且可能遍历到被添加到原型上的属性。
面试题 2:以下代码输出什么?为什么?
javascript
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
答案 :输出 3 3 3。
解析 :var 声明的 i 是函数作用域(或全局作用域),三次循环共享同一个 i 变量。当 setTimeout 回调执行时,循环已经结束,i 的值为 3。
修复方案:
javascript
// 方案1:使用 let(块级作用域)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0 1 2
}
// 方案2:使用闭包
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 100);
})(i);
}
面试题 3:try...catch 能捕获所有错误吗?
参考答案:
不能。try...catch 只能捕获同步代码中的运行时错误。以下情况无法捕获:
- 异步错误 :
setTimeout、Promise等异步回调中的错误。 - 语法错误 :代码解析阶段的
SyntaxError(eval中的除外)。
javascript
// ❌ 无法捕获异步错误
try {
setTimeout(() => {
throw new Error('异步错误'); // 不会被 catch 捕获
}, 100);
} catch (error) {
console.log('捕获不到');
}
// ✅ 异步错误需要单独处理
setTimeout(() => {
try {
throw new Error('异步错误');
} catch (error) {
console.log('捕获到了:', error.message);
}
}, 100);
面试题 4:switch 使用 == 还是 === 比较?
答案 :switch 使用 ===(严格相等)进行比较,不会发生类型转换。
javascript
const value = 0;
switch (value) {
case false:
console.log('false'); // 不会执行(0 !== false)
break;
case '':
console.log('空字符串'); // 不会执行(0 !== '')
break;
case 0:
console.log('零'); // ✅ 执行(0 === 0)
break;
}
本章小结
| 知识点 | 要点 |
|---|---|
| if...else | 最通用的条件语句,支持范围判断和复杂条件 |
| switch | 适合对同一值的多个等值分支,使用 === 比较 |
| 三元运算符 | 简洁的条件表达式,适合简单赋值 |
| for 循环 | 已知循环次数时首选,内置初始化/条件/更新 |
| while 循环 | 不确定循环次数时首选,先判断后执行 |
| do...while | 保证至少执行一次,先执行后判断 |
| for...of | 遍历可迭代对象的值,推荐用于数组 |
| for...in | 遍历对象的键,不推荐用于数组 |
| break | 跳出当前循环或 switch |
| continue | 跳过本次迭代,进入下一次 |
| 标签语句 | 在嵌套循环中指定 break/continue 的目标层 |
| try...catch | 捕获同步代码的运行时异常 |
| finally | 无论是否异常都执行,常用于资源清理 |
| throw | 主动抛出异常,推荐抛出 Error 对象 |
一句话总结 :流程控制是程序的"骨架"。
if...else和switch决定走哪条路,for和while决定重复多少次,break和continue控制循环的节奏,try...catch...finally确保程序在出错时也能优雅地运行。
下篇预告 :第7章:函数基础 --- 我们将学习函数的声明方式、箭头函数、参数处理、高阶函数以及函数式编程的基本工具。
作者 :xhmico | CSDN 博客 :https://blog.csdn.net/xhmico
参考资源 :The Modern JavaScript Tutorial | MDN Web Docs | ECMAScript 规范