javascript:第三章控制流
目录
第三章 控制流
3.1 程序控制流概述
程序控制流决定了代码执行的顺序。默认情况下,JavaScript代码从上到下顺序执行,但通过控制流语句,我们可以:
- 根据条件选择执行不同的代码分支
- 重复执行某段代码
- 跳过某些代码或提前退出
控制流语句分为三类:
- 条件语句:if…else、switch
- 循环语句:while、do…while、for、for…in、for…of
- 跳转语句:break、continue、return、throw
3.2 条件语句
3.2.1 if语句
最基本的条件语句,根据条件决定是否执行代码块。
// 基本if语句 let age = 20; if (age >= 18) { console.log("已成年"); } // if...else语句 let score = 75; if (score >= 60) { console.log("及格"); } else { console.log("不及格"); } // if...else if...else语句 let grade = 85; if (grade >= 90) { console.log("优秀"); } else if (grade >= 80) { console.log("良好"); } else if (grade >= 70) { console.log("中等"); } else if (grade >= 60) { console.log("及格"); } else { console.log("不及格"); }
条件表达式求值:
// 条件会被转换为布尔值 if (0) { } // 不执行 if ("") { } // 不执行 if (null) { } // 不执行 if (undefined) { } // 不执行 if (NaN) { } // 不执行 if (false) { } // 不执行 if (1) { } // 执行 if ("hello") { } // 执行 if ({}) { } // 执行 if ([]) { } // 执行
3.2.2 嵌套if语句
let isLoggedIn = true; let isAdmin = false; if (isLoggedIn) { console.log("欢迎回来"); if (isAdmin) { console.log("管理员权限"); } else { console.log("普通用户权限"); } } else { console.log("请先登录"); } // 更好的写法:使用逻辑运算符 if (isLoggedIn && isAdmin) { console.log("欢迎管理员"); } else if (isLoggedIn) { console.log("欢迎用户"); } else { console.log("请先登录"); }
3.2.3 switch语句
当需要对同一个表达式的多个可能值进行判断时,switch语句通常比if…else更清晰。
let day = 3; let dayName; switch (day) { case 1: dayName = "星期一"; break; case 2: dayName = "星期二"; break; case 3: dayName = "星期三"; break; case 4: dayName = "星期四"; break; case 5: dayName = "星期五"; break; case 6: case 7: dayName = "周末"; break; default: dayName = "无效日期"; } console.log(dayName); // "星期三"
switch语句要点:
- 使用严格相等(===)进行比较
- case值可以是表达式,但会预先求值
- 多个case可以共享代码(fall-through)
- default是可选的,可以放在任意位置
- 忘记break会导致case穿透
// case穿透示例(有时是有意的) let fruit = "apple"; let type; switch (fruit) { case "apple": case "pear": case "peach": type = "梨果类"; break; case "orange": case "lemon": case "grapefruit": type = "柑橘类"; break; default: type = "其他"; }
3.2.4 条件表达式的替代写法
// 三元运算符替代简单if...else let age = 20; let status = age >= 18 ? "成年" : "未成年"; // 逻辑与替代简单if isValid && processData(); // 等价于 if (isValid) { processData(); } // 逻辑或提供默认值 let name = inputName || "Anonymous"; // 空值合并(ES2020) let count = userCount ?? 0; // 只在null/undefined时取默认值
3.3 循环语句
3.3.1 while循环
先检查条件,条件为真时执行循环体。
// 基本while循环 let count = 0; while (count < 5) { console.log(count); // 0, 1, 2, 3, 4 count++; } // 读取用户输入直到有效(示例概念) let input; while (!isValid(input)) { input = getInput(); } // 无限循环(需要break退出) while (true) { // 处理任务 if (shouldStop()) { break; } }
3.3.2 do...while循环
先执行一次循环体,再检查条件。确保循环体至少执行一次。
// 基本do...while循环 let i = 0; do { console.log(i); // 0, 1, 2, 3, 4 i++; } while (i < 5); // 至少执行一次的菜单系统 let choice; do { displayMenu(); choice = getUserChoice(); processChoice(choice); } while (choice !== "quit");
3.3.3 for循环
最常用的循环结构,包含初始化、条件、迭代三个部分。
// 基本for循环 for (let i = 0; i < 5; i++) { console.log(i); // 0, 1, 2, 3, 4 } // 循环的组成部分都是可选的 let j = 0; for (; j < 5;) { console.log(j); j++; } // 多个初始化表达式 for (let i = 0, j = 10; i < j; i++, j--) { console.log(i, j); } // 递减循环 for (let i = 10; i > 0; i--) { console.log(i); // 10, 9, 8, ..., 1 } // 步长不为1 for (let i = 0; i < 20; i += 2) { console.log(i); // 0, 2, 4, 6, ..., 18 }
循环内的变量作用域:
// let声明的变量有块级作用域 for (let i = 0; i < 3; i++) { console.log(i); // 0, 1, 2 } // console.log(i); // ReferenceError: i is not defined // var声明的变量有函数作用域 for (var j = 0; j < 3; j++) { console.log(j); // 0, 1, 2 } console.log(j); // 3 // let在循环中的闭包问题 for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); // 3, 3, 3 } for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); // 0, 1, 2 }
3.3.4 for...in循环
遍历对象的可枚举属性(包括继承的属性)。
// 遍历对象 let person = { name: "张三", age: 25, city: "北京" }; for (let key in person) { console.log(key + ": " + person[key]); } // name: 张三 // age: 25 // city: 北京 // 检查是否是自身属性(非继承) for (let key in person) { if (person.hasOwnProperty(key)) { console.log(key + ": " + person[key]); } }
for…in用于数组的问题:
// 可以遍历数组,但不推荐 let arr = ["a", "b", "c"]; for (let index in arr) { console.log(index, arr[index]); // index是字符串"0", "1", "2" } // 问题:会遍历非数字属性和原型链属性 Array.prototype.custom = "custom"; for (let i in arr) { console.log(i); // 0, 1, 2, custom } // 更好的数组遍历方式:for...of 或 forEach
3.3.5 for...of循环(ES6)
遍历可迭代对象(数组、字符串、Map、Set、NodeList等)。
// 遍历数组 let colors = ["red", "green", "blue"]; for (let color of colors) { console.log(color); // red, green, blue } // 遍历字符串 for (let char of "Hello") { console.log(char); // H, e, l, l, o } // 遍历Map let map = new Map([["a", 1], ["b", 2]]); for (let [key, value] of map) { console.log(key, value); } // 使用entries()获取索引和值 for (let [index, value] of colors.entries()) { console.log(index, value); } // 使用解构 let users = [ { name: "Alice", age: 25 }, { name: "Bob", age: 30 } ]; for (let { name, age } of users) { console.log(`${name} is ${age} years old`); }
for…of vs for…in:
| 特性 | for…in | for…of |
| —— | ———- | ———- |
| 用途 | 遍历对象属性 | 遍历可迭代对象的值 |
| 数组索引 | 字符串 | 数字 |
| 遍历原型链 | 是 | 否 |
| Symbol属性 | 否 | 是 |
3.4 跳转语句
3.4.1 break语句
立即终止当前循环或switch语句。
// 在循环中使用break for (let i = 0; i < 10; i++) { if (i === 5) { break; // 退出循环,不再执行 } console.log(i); // 0, 1, 2, 3, 4 } // 在switch中使用break(前面已有示例) // 在while中使用break let num = 1; while (true) { if (num > 100) { break; } num *= 2; } console.log(num); // 128
标签语句与break:
// 给循环添加标签 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
3.4.2 continue语句
跳过当前迭代,继续下一次循环。
// 跳过偶数 for (let i = 0; i < 10; i++) { if (i % 2 === 0) { continue; // 跳过本次循环的剩余部分 } console.log(i); // 1, 3, 5, 7, 9 } // 处理数组时跳过某些元素 let numbers = [1, 2, null, 4, undefined, 6]; let sum = 0; for (let num of numbers) { if (num === null || num === undefined) { continue; } sum += num; } console.log(sum); // 13
continue与标签:
outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (j === 1) { continue outer; // 跳到外层循环的下一次迭代 } console.log(i, j); } } // 输出: // 0 0 // 1 0 // 2 0
3.4.3 return语句
从函数中返回一个值,并终止函数执行。
// 基本return function add(a, b) { return a + b; } // 提前返回 function divide(a, b) { if (b === 0) { return null; // 提前返回,不执行后面的代码 } return a / b; } // 无返回值(或返回undefined) function log(message) { console.log(message); return; // 可选,函数末尾自动返回undefined } // 返回多个值(通过对象或数组) function getMinMax(arr) { return { min: Math.min(...arr), max: Math.max(...arr) }; } let { min, max } = getMinMax([3, 1, 4, 1, 5]);
3.5 异常处理
3.5.1 try...catch...finally
try { // 尝试执行的代码 let result = riskyOperation(); console.log(result); } catch (error) { // 发生错误时执行 console.error("发生错误:", error.message); } finally { // 无论是否发生错误都执行 console.log("清理资源"); }
catch块参数:
// 捕获错误对象 try { JSON.parse("invalid json"); } catch (e) { console.log(e.name); // "SyntaxError" console.log(e.message); // 错误信息 console.log(e.stack); // 错误堆栈 } // ES2019可选绑定(不需要错误对象时) try { riskyOperation(); } catch { console.log("出错了,但不需要错误详情"); }
3.5.2 抛出异常
// 抛出Error对象 function divide(a, b) { if (b === 0) { throw new Error("除数不能为零"); } return a / b; } // 使用特定的错误类型 function validateAge(age) { if (typeof age !== 'number') { throw new TypeError("年龄必须是数字"); } if (age < 0 || age > 150) { throw new RangeError("年龄必须在0-150之间"); } } // 自定义错误类型 class ValidationError extends Error { constructor(message, field) { super(message); this.name = "ValidationError"; this.field = field; } } throw new ValidationError("用户名不能为空", "username");
3.6 循环优化与最佳实践
3.6.1 循环性能优化
// 1. 缓存数组长度 // 不推荐 for (let i = 0; i < array.length; i++) { } // 推荐 for (let i = 0, len = array.length; i < len; i++) { } // 或者反向循环 for (let i = array.length - 1; i >= 0; i--) { } // 2. 避免在循环中创建函数 // 不推荐 for (let i = 0; i < 10; i++) { setTimeout(() => console.log(i), 100); // 每次都创建新函数 } // 推荐:使用let块级作用域,或提取函数 for (let i = 0; i < 10; i++) { setTimeout(createLogger(i), 100); } function createLogger(i) { return () => console.log(i); } // 3. 使用合适的数据结构和方法 // 使用forEach、map、filter等替代手动循环 array.forEach(item => console.log(item)); let doubled = array.map(x => x * 2); let evens = array.filter(x => x % 2 === 0);
3.6.2 循环模式
遍历数组的所有方法:
let numbers = [1, 2, 3, 4, 5]; // for循环 for (let i = 0; i < numbers.length; i++) { console.log(numbers[i]); } // for...of(推荐用于遍历值) for (let num of numbers) { console.log(num); } // forEach(不能break或return跳出) numbers.forEach((num, index) => { console.log(index, num); }); // entries()获取索引和值 for (let [index, value] of numbers.entries()) { console.log(index, value); } // some(找到满足条件的就停止) numbers.some(num => { console.log(num); return num > 3; // num > 3时停止 }); // every(找到不满足条件的就停止) numbers.every(num => { console.log(num); return num < 3; // num >= 3时停止 });
3.7 练习题
练习1:条件语句
编写一个函数 `getDayType`,根据数字返回星期类型:
// 1-5: 工作日 // 6-7: 周末 // 其他: 无效输入 getDayType(1); // "工作日" getDayType(6); // "周末" getDayType(0); // "无效输入" // 使用if...else和switch两种写法
练习2:循环应用
完成以下循环练习:
// 1. 使用for循环计算1到100的和 // 2. 使用while循环找出100以内的所有质数 // 3. 使用do...while实现一个简单的猜数字游戏 // 生成1-100的随机数,让用户猜,提示太大或太小,直到猜中 // 4. 使用嵌套循环打印乘法表 // 1*1=1 // 1*2=2 2*2=4 // ...
练习3:break和continue
分析以下代码的输出:
// 代码1 for (let i = 0; i < 5; i++) { if (i === 2) continue; if (i === 4) break; console.log(i); } // 代码2 outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { if (i + j === 2) break outer; console.log(i, j); } } // 代码3 let arr = [1, 2, 3, 4, 5]; for (let i = 0; i < arr.length; i++) { if (arr[i] % 2 === 0) { arr.splice(i, 1); i--; } } console.log(arr);
练习4:综合练习
编写一个函数 `fizzBuzz`,接收一个数字n,打印1到n:
- 如果数字能被3整除,打印“Fizz”
- 如果数字能被5整除,打印“Buzz”
- 如果同时能被3和5整除,打印“FizzBuzz”
- 否则打印数字本身
然后优化这个函数,使其可以接收任意数量的规则(数字和对应的字符串)。
参考答案
练习1答案:
// if...else写法 function getDayTypeIf(day) { if (day >= 1 && day <= 5) { return "工作日"; } else if (day === 6 || day === 7) { return "周末"; } else { return "无效输入"; } } // switch写法 function getDayTypeSwitch(day) { switch (day) { case 1: case 2: case 3: case 4: case 5: return "工作日"; case 6: case 7: return "周末"; default: return "无效输入"; } }
练习2答案:
// 1. 1到100的和 let sum = 0; for (let i = 1; i <= 100; i++) { sum += i; } console.log(sum); // 5050 // 2. 100以内的质数 function isPrime(n) { if (n < 2) return false; for (let i = 2; i <= Math.sqrt(n); i++) { if (n % i === 0) return false; } return true; } let n = 2; while (n < 100) { if (isPrime(n)) { console.log(n); } n++; } // 3. 猜数字游戏(简化版) function guessNumberGame() { const target = Math.floor(Math.random() * 100) + 1; let guess; let attempts = 0; do { guess = parseInt(prompt("猜一个1-100的数字:")); attempts++; if (guess > target) { alert("太大了!"); } else if (guess < target) { alert("太小了!"); } } while (guess !== target); alert(`恭喜你猜对了!答案是${target},你用了${attempts}次。`); } // 4. 乘法表 for (let i = 1; i <= 9; i++) { let row = ""; for (let j = 1; j <= i; j++) { row += `${j}*${i}=${i*j}\t`; } console.log(row); }
练习3答案:
// 代码1:输出 0, 1, 3 // i=0: 输出0 // i=1: 输出1 // i=2: continue,跳过 // i=3: 输出3 // i=4: break,退出 // 代码2:输出 (0,0), (0,1) // i=0,j=0: 0+0=0,输出 // i=0,j=1: 0+1=1,输出 // i=0,j=2: 0+2=2,break outer // 代码3:输出 [1, 3, 5] // 删除元素时调整索引,避免跳过元素
练习4答案:
// 基础版 function fizzBuzz(n) { for (let i = 1; i <= n; i++) { if (i % 3 === 0 && i % 5 === 0) { console.log("FizzBuzz"); } else if (i % 3 === 0) { console.log("Fizz"); } else if (i % 5 === 0) { console.log("Buzz"); } else { console.log(i); } } } // 优化版:支持任意规则 function fizzBuzzAdvanced(n, rules) { // rules: [{ divisor: 3, word: "Fizz" }, { divisor: 5, word: "Buzz" }] for (let i = 1; i <= n; i++) { let output = ""; for (let rule of rules) { if (i % rule.divisor === 0) { output += rule.word; } } console.log(output || i); } } // 使用 fizzBuzzAdvanced(30, [ { divisor: 3, word: "Fizz" }, { divisor: 5, word: "Buzz" }, { divisor: 7, word: "Whizz" } ]);
本章小结
本章学习了JavaScript的控制流语句:
- 条件语句:if…else、switch、三元运算符
- 循环语句:while、do…while、for、for…in、for…of
- 跳转语句:break、continue、return
- 异常处理:try…catch…finally、throw
关键要点:
- 根据场景选择合适的循环结构
- for…of适合遍历可迭代对象的值
- for…in适合遍历对象属性(数组不推荐)
- 合理使用break和continue控制循环流程
- 掌握异常处理机制编写健壮的代码
下一章将学习函数,这是JavaScript中最重要的概念之一。
javascript/第三章控制流.txt · 最后更改: 由 127.0.0.1
