lesson59:JavaScript 控制流详解:分支结构与循环语句全指南
目录
一、分支结构:让程序学会"做选择"
1. if 语句:灵活的条件判断
1.1 常见形式与应用场景
1.2 嵌套 if 与注意事项
2. switch 语句:多值匹配的高效方案
2.1 核心特性与示例
2.2 if-else vs switch:如何选择?
二、循环语句:高效处理重复任务
1. for 循环:最经典的计数循环
1.1 执行流程解析
1.2 基础用法与变种
2. while 循环:条件驱动的循环
2.1 典型应用场景
2.2 警惕无限循环!
3. do-while 循环:至少执行一次的循环
3.1 适用场景:先执行后判断
4. 现代循环:for...of 与 for...in(ES6+)
4.1 for...of:遍历可迭代对象
4.2 for...in:遍历对象属性
5. 循环选择指南:哪种循环最适合你?
三、循环控制与高级技巧
1. 循环控制语句:break 与 continue
2. 标签语句:跳出多层循环
3. 性能优化:减少循环开销
四、总结与最佳实践
核心知识点回顾
编写高质量代码的建议
在 JavaScript 编程中,控制流是构建逻辑的核心。无论是根据用户输入展示不同内容,还是处理大量数据,都离不开分支结构和循环语句的灵活运用。本文将系统讲解 JavaScript 中的分支结构(if/switch)与循环语句(for/while/do-while),结合实例分析其适用场景与最佳实践,帮助你写出更高效、更可读的代码。
一、分支结构:让程序学会"做选择"
分支结构允许程序根据不同条件执行不同代码块,是实现"决策逻辑"的基础。JavaScript 提供两种主要分支结构:if 语句和switch 语句。
1. if 语句:灵活的条件判断
if 语句是最常用的分支结构,通过布尔条件决定代码执行路径。其基本语法如下:
// 基础语法
if (条件表达式) {
// 条件为 true 时执行
} else if (条件表达式2) {
// 条件1为 false,条件2为 true 时执行
} else {
// 所有条件都为 false 时执行
}
1.1 常见形式与应用场景
-
单条件判断(if):适用于单一条件场景,如用户登录验证
const isLoggedIn = checkUserStatus(); if (isLoggedIn) { renderDashboard(); // 用户已登录,渲染控制台 }
-
二选一判断(if-else):处理互斥条件,如权限控制
if (user.role === 'admin') { showAdminPanel(); // 管理员显示管理面板 } else { showUserPanel(); // 普通用户显示用户面板 }
-
多条件判断(if-else if-else):处理多种可能结果,如成绩评级
const score = 85; if (score >= 90) { console.log('优秀'); } else if (score >= 80) { console.log('良好'); // 输出:良好 } else if (score >= 60) { console.log('及格'); } else { console.log('不及格'); }
1.2 嵌套 if 与注意事项
嵌套 if 可处理复杂条件,但过度嵌套会导致"回调地狱"式的代码(如 if (a) { if (b) { if (c) { ... } } }
)。建议:
- 条件合并:用逻辑运算符(
&&
/||
/!
)简化多层条件 - 提前返回:将简单条件放在前面,减少嵌套层级
- 使用三元表达式:处理简单二选一逻辑(但避免嵌套三元)
// 优化前:嵌套 if
if (user) {
if (user.isVerified) {
if (user.hasPermission) {
allowAccess();
} else {
showError('无权限');
}
} else {
showError('未验证');
}
} else {
showError('未登录');
}// 优化后:提前返回 + 条件合并
if (!user) return showError('未登录');
if (!user.isVerified) return showError('未验证');
if (!user.hasPermission) return showError('无权限');
allowAccess();
2. switch 语句:多值匹配的高效方案
当需要根据固定值执行不同逻辑时,switch 比 if-else 更简洁。其语法结构如下:
switch (表达式) {
case 值1:
// 表达式 === 值1 时执行
break; // 跳出 switch
case 值2:
// 表达式 === 值2 时执行
break;
default:
// 所有 case 不匹配时执行(可选)
}
2.1 核心特性与示例
- 严格匹配:使用
===
比较,类型和值必须完全一致 - case 穿透:缺少
break
会继续执行下一个 case(可利用此特性合并逻辑) - default 子句:处理未匹配的情况,建议始终添加以避免逻辑遗漏
// 根据月份判断季节(利用穿透特性)
const month = 3;
switch (month) {
case 12:
case 1:
case 2:
console.log('冬季');
break;
case 3: // 3月匹配此 case,因无 break 会穿透到 case 4
case 4:
case 5:
console.log('春季'); // 输出:春季
break;
case 6:
case 7:
case 8:
console.log('夏季');
break;
case 9:
case 10:
case 11:
console.log('秋季');
break;
default:
console.log('无效月份');
}
2.2 if-else vs switch:如何选择?
场景 | 推荐使用 | 原因 |
---|---|---|
范围条件(如 x > 10 ) | if-else | switch 仅支持固定值匹配,不支持范围判断 |
固定值匹配(如状态码) | switch | 代码更简洁,避免冗长的 else if (x === value) |
少量条件(≤3个) | if-else | 无需额外语法(break/default),可读性更高 |
多条件且值离散 | switch | 执行效率更高(引擎可优化为跳转表),且逻辑分组更清晰 |
二、循环语句:高效处理重复任务
循环语句用于重复执行代码块,是处理数组、集合等数据的必备工具。JavaScript 提供四种循环方式:for
、while
、do-while
及 for...of
/for...in
(ES6+)。
1. for 循环:最经典的计数循环
for 循环适合已知循环次数的场景,语法结构清晰,控制力强:
for (初始化表达式; 条件表达式; 增量表达式) {
// 循环体
}
1.1 执行流程解析
- 初始化:执行一次(如
let i = 0
) - 条件判断:若为 true,执行循环体;否则退出循环
- 增量更新:执行循环体后更新变量(如
i++
) - 重复步骤 2-3
1.2 基础用法与变种
-
标准 for 循环:遍历数组(最常用场景)
const fruits = ['苹果', '香蕉', '橙子']; for (let i = 0; i < fruits.length; i++) { console.log(`第${i+1}个水果:${fruits[i]}`); } // 输出: // 第1个水果:苹果 // 第2个水果:香蕉 // 第3个水果:橙子
-
反向循环:从数组末尾开始遍历
for (let i = fruits.length - 1; i >= 0; i--) { console.log(fruits[i]); // 橙子、香蕉、苹果 }
-
跳过/终止循环:使用
continue
(跳过当前迭代)和break
(终止整个循环)// 求1-100间偶数之和(跳过奇数) let sum = 0; for (let i = 1; i <= 100; i++) { if (i % 2 !== 0) continue; // 跳过奇数 sum += i; if (sum > 1000) break; // 超过1000时提前终止 } console.log(sum); // 1050(1+3+...+45=1035,+47=1082>1000,故终止于46的和1015?需计算验证)
-
无限循环:需谨慎使用,必须在循环体内有退出条件
for (;;) { // 等价于 while(true) const input = prompt('输入"quit"退出'); if (input === 'quit') break; console.log('你输入了:', input); }
2. while 循环:条件驱动的循环
while 循环适合循环次数不确定的场景,仅在条件为 true 时执行:
while (条件表达式) {
// 循环体(需包含条件更新逻辑,避免无限循环)
}
2.1 典型应用场景
-
等待某个条件满足:如轮询等待数据加载完成
let data = null; while (!data) { data = fetchData(); // 假设 fetchData() 可能返回 null if (!data) await sleep(1000); // 未获取到数据,等待1秒重试 } processData(data);
-
用户输入验证:确保输入符合要求
let age; while (isNaN(age) || age < 0 || age > 120) { age = Number(prompt('请输入有效年龄(0-120):')); } console.log('你的年龄是:', age);
2.2 警惕无限循环!
while 循环最常见的错误是条件永远为 true,导致浏览器卡死。避免方式:
- 确保循环体内有修改条件的逻辑(如
count++
) - 添加安全机制(如最大重试次数):
let retryCount = 0; while (retryCount < 5) { // 最多重试5次 if (connect()) break; retryCount++; await sleep(1000); } if (retryCount === 5) console.error('连接失败');
3. do-while 循环:至少执行一次的循环
do-while 是后测试循环,无论条件如何,循环体至少执行一次:
do {
// 循环体
} while (条件表达式); // 注意末尾分号
3.1 适用场景:先执行后判断
- 初始化操作:如加载配置文件,即使配置为空也需执行一次解析
- 菜单交互:确保用户至少看到一次菜单选项
let choice; do { console.log(`1. 查看信息\n2. 修改信息\n3. 退出`); choice = prompt('请选择操作(1-3):'); switch (choice) { case '1': showInfo(); break; case '2': editInfo(); break; case '3': console.log('退出程序'); break; default: alert('无效选项'); } } while (choice !== '3'); // 选择3时退出
4. 现代循环:for...of 与 for...in(ES6+)
ES6 引入了更简洁的循环语法,尤其适合遍历数据集合。
4.1 for...of:遍历可迭代对象
用于遍历数组、字符串、Map、Set等可迭代对象(Iterable
),直接获取值而非索引:
// 遍历数组
const numbers = [10, 20, 30];
for (const num of numbers) {
console.log(num * 2); // 20、40、60
}// 遍历字符串
for (const char of 'hello') {
console.log(char.toUpperCase()); // H、E、L、L、O
}// 遍历 Map
const user = new Map([['name', '张三'], ['age', 25]]);
for (const [key, value] of user) {
console.log(`${key}: ${value}`); // name: 张三,age: 25
}
4.2 for...in:遍历对象属性
用于遍历对象的可枚举属性(包括继承的属性),返回属性名:
const person = { name: '李四', age: 30, city: '北京' };
for (const key in person) {
if (person.hasOwnProperty(key)) { // 过滤继承属性
console.log(`${key}: ${person[key]}`);
}
}
// 输出:
// name: 李四
// age: 30
// city: 北京
⚠️ 注意:
- 不要用
for...in
遍历数组(可能遍历到非数字索引的属性) - 始终用
hasOwnProperty
过滤原型链属性
5. 循环选择指南:哪种循环最适合你?
场景 | 推荐循环 | 优势 |
---|---|---|
遍历数组(已知索引) | for / for...of | for 可控制步长(如 i+=2),for...of 更简洁 |
遍历对象属性 | for...in | 专门为对象属性设计,返回键名 |
遍历可迭代对象(Map/Set/字符串) | for...of | 直接获取值,支持解构赋值 |
循环次数不确定(条件控制) | while | 逻辑更清晰,避免冗余的初始化代码 |
至少执行一次 | do-while | 无需提前初始化条件变量 |
异步循环(如等待Promise) | for...of + async/await | 支持 await 语法(普通 for 循环也可) |
三、循环控制与高级技巧
1. 循环控制语句:break 与 continue
-
break:立即终止整个循环,跳到循环后的代码
// 查找数组中第一个偶数 const nums = [1, 3, 5, 4, 7]; let firstEven; for (const num of nums) { if (num % 2 === 0) { firstEven = num; break; // 找到后立即退出循环 } } console.log(firstEven); // 4
-
continue:跳过当前迭代,直接进入下一次循环
// 计算数组中奇数之和 const nums = [1, 2, 3, 4, 5]; let sum = 0; for (const num of nums) { if (num % 2 === 0) continue; // 跳过偶数 sum += num; } console.log(sum); // 1+3+5=9
2. 标签语句:跳出多层循环
嵌套循环中,可通过标签语句跳出外层循环(不推荐过度使用,建议拆分为函数):
outerLoop: for (let i = 0; i < 3; i++) {
innerLoop: for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
break outerLoop; // 直接跳出 outerLoop
}
console.log(`i=${i}, j=${j}`);
}
}
// 输出:
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0(然后 break outerLoop)
3. 性能优化:减少循环开销
-
缓存数组长度:避免每次循环都计算
arr.length
// 优化前 for (let i = 0; i < arr.length; i++) { ... }// 优化后 for (let i = 0, len = arr.length; i < len; i++) { ... }
-
避免在循环中操作 DOM:DOM 操作昂贵,建议批量处理
// 优化前(每次循环操作DOM) for (const item of list) { document.getElementById('container').innerHTML += `<div>${item}</div>`; }// 优化后(先拼接字符串,再一次性更新) let html = ''; for (const item of list) { html += `<div>${item}</div>`; } document.getElementById('container').innerHTML = html;
-
使用 typedArray:处理大量数字时,
Uint8Array
等类型数组比普通数组更快
四、总结与最佳实践
核心知识点回顾
- 分支结构:用
if-else
处理灵活条件,switch
处理固定值匹配 - 循环语句:
for
适合计数,while
适合条件控制,for...of
适合遍历数据 - 控制流:
break
终止循环,continue
跳过迭代,标签语句处理嵌套循环
编写高质量代码的建议
- 可读性优先:避免过度嵌套(分支/循环),复杂逻辑拆分为函数
- 选择合适工具:根据场景选择分支/循环类型(如范围条件用 if,固定值用 switch)
- 防御性编程:循环添加边界条件,避免无限循环;分支添加 default 处理异常情况
- 利用现代语法:优先使用
for...of
、const/let
等 ES6+ 特性,减少 var 导致的作用域问题 - 代码复用:重复逻辑抽象为函数(如将循环逻辑封装为工具函数)
控制流是 JavaScript 的骨架,掌握分支与循环不仅能解决当前问题,更能培养结构化思维。通过大量实践(如实现排序算法、处理API数据),你将能灵活运用这些工具,写出高效、优雅的代码!
下一步学习建议:结合函数、数组方法(forEach
/map
/filter
)深入理解声明式编程,对比命令式循环的优劣。