流程控制概述
流程控制语句用于控制代码的执行顺序,主要包括:
- 条件语句 - 根据条件执行不同的代码块
- 循环语句 - 重复执行代码块
- 跳转语句 - 改变代码的正常执行流程
- 错误处理语句 - 处理程序执行过程中的错误
程序执行流程
JavaScript程序默认按照从上到下的顺序执行,但可以通过流程控制语句改变这种顺序:
- 顺序执行 - 默认的执行方式
- 条件分支 - 根据条件选择执行路径
- 循环执行 - 重复执行某段代码
- 异常处理 - 处理执行过程中的错误
条件语句
if 语句
if 语句
// 基本 if 语句
let age = 18;
if (age >= 18) {
console.log("您已成年");
}
// if...else 语句
let temperature = 25;
if (temperature > 30) {
console.log("天气很热");
} else {
console.log("天气舒适");
}
// if...else if...else 语句
let score = 85;
if (score >= 90) {
console.log("优秀");
} else if (score >= 80) {
console.log("良好");
} else if (score >= 60) {
console.log("及格");
} else {
console.log("不及格");
}
// 嵌套 if 语句
let isMember = true;
let orderAmount = 200;
if (isMember) {
if (orderAmount > 100) {
console.log("享受会员折扣");
} else {
console.log("订单金额不足,无法享受折扣");
}
} else {
console.log("非会员用户");
}
switch 语句
switch 语句
let day = "Monday";
let dayType;
switch (day) {
case "Monday":
case "Tuesday":
case "Wednesday":
case "Thursday":
case "Friday":
dayType = "工作日";
break;
case "Saturday":
case "Sunday":
dayType = "周末";
break;
default:
dayType = "未知";
}
console.log(dayType); // "工作日"
// switch 语句的 fall-through 特性
let month = 2;
let year = 2020;
let days;
switch (month) {
case 1: case 3: case 5: case 7:
case 8: case 10: case 12:
days = 31;
break;
case 4: case 6: case 9: case 11:
days = 30;
break;
case 2:
// 检查是否为闰年
days = (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0) ? 29 : 28;
break;
default:
days = -1; // 无效月份
}
console.log(`${year}年${month}月有 ${days} 天`);
注意:
在switch语句中不要忘记使用
break,否则会继续执行后续的case。但有时故意省略break可以实现多个case共享同一段代码。
条件(三元)运算符
三元运算符
// 基本用法
let age = 20;
let status = age >= 18 ? "成年人" : "未成年人";
// 嵌套三元运算符
let score = 85;
let grade = score >= 90 ? "A" :
score >= 80 ? "B" :
score >= 70 ? "C" : "D";
// 与函数调用结合
function adultMessage() {
return "欢迎成年人";
}
function minorMessage() {
return "未成年人需家长陪同";
}
let message = age >= 18 ? adultMessage() : minorMessage();
循环语句
for 循环
for 循环
// 基本 for 循环
for (let i = 0; i < 5; i++) {
console.log("循环次数: " + i);
}
// 输出: 0, 1, 2, 3, 4
// 遍历数组
let fruits = ["苹果", "香蕉", "橙子"];
for (let i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
// 多个循环变量
for (let i = 0, j = 10; i < j; i++, j--) {
console.log(`i=${i}, j=${j}`);
}
// 省略部分表达式
let k = 0;
for (; k < 3;) {
console.log(k);
k++;
}
while 循环
while 循环
// while 循环
let count = 0;
while (count < 3) {
console.log("count: " + count);
count++;
}
// 输出: 0, 1, 2
// do...while 循环 (至少执行一次)
let num = 5;
do {
console.log("num: " + num);
num--;
} while (num > 0);
// 输出: 5, 4, 3, 2, 1
// 无限循环 (需要谨慎使用)
// while (true) {
// // 无限循环体
// }
// 使用 while 循环处理用户输入
// let userInput;
// while (userInput !== "quit") {
// userInput = prompt("请输入命令 (输入 'quit' 退出):");
// console.log(`您输入了: ${userInput}`);
// }
for...of 循环 (ES6)
for...of 循环
// 遍历数组 (推荐)
let colors = ["red", "green", "blue"];
for (let color of colors) {
console.log(color);
}
// 输出: red, green, blue
// 遍历字符串
let str = "Hello";
for (let char of str) {
console.log(char);
}
// 输出: H, e, l, l, o
// 遍历 Set
let uniqueNumbers = new Set([1, 2, 3, 2, 1]);
for (let num of uniqueNumbers) {
console.log(num);
}
// 输出: 1, 2, 3
// 遍历 Map
let userMap = new Map([
["name", "John"],
["age", 30]
]);
for (let [key, value] of userMap) {
console.log(`${key}: ${value}`);
}
for...in 循环
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遍历数组,因为它会遍历数组的所有可枚举属性,包括原型链上的属性。应该使用for...of或传统的for循环来遍历数组。
跳转语句
break 和 continue
break 和 continue
// break - 立即退出循环
for (let i = 0; i < 10; i++) {
if (i === 5) {
break; // 当 i 等于 5 时退出循环
}
console.log(i);
}
// 输出: 0, 1, 2, 3, 4
// continue - 跳过当前迭代,继续下一次
for (let i = 0; i < 5; i++) {
if (i === 2) {
continue; // 跳过 i 等于 2 的情况
}
console.log(i);
}
// 输出: 0, 1, 3, 4
// 标签与 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=${i}, j=${j}`);
}
}
// 标签与 continue
outer:
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) {
continue outer; // 继续外层循环的下一次迭代
}
console.log(`i=${i}, j=${j}`);
}
}
return 语句
return 语句
// 在函数中使用 return
function checkAge(age) {
if (age < 0) {
return "年龄不能为负数";
}
if (age < 18) {
return "未成年人";
}
return "成年人";
}
console.log(checkAge(15)); // "未成年人"
console.log(checkAge(25)); // "成年人"
console.log(checkAge(-5)); // "年龄不能为负数"
现代循环方法 (ES5+)
数组迭代方法
数组迭代方法
let numbers = [1, 2, 3, 4, 5];
// forEach - 遍历数组
numbers.forEach(function(number, index) {
console.log(`索引 ${index}: 值 ${number}`);
});
// map - 创建新数组
let doubled = numbers.map(function(number) {
return number * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - 过滤数组
let evenNumbers = numbers.filter(function(number) {
return number % 2 === 0;
});
console.log(evenNumbers); // [2, 4]
// reduce - 累计计算
let sum = numbers.reduce(function(accumulator, current) {
return accumulator + current;
}, 0);
console.log(sum); // 15
// find - 查找元素
let found = numbers.find(function(number) {
return number > 3;
});
console.log(found); // 4
// some - 检查是否有元素满足条件
let hasEven = numbers.some(function(number) {
return number % 2 === 0;
});
console.log(hasEven); // true
// every - 检查所有元素是否满足条件
let allPositive = numbers.every(function(number) {
return number > 0;
});
console.log(allPositive); // true
错误处理
try...catch 语句
try...catch
// 基本错误处理
try {
// 可能抛出错误的代码
let result = riskyOperation();
console.log(result);
} catch (error) {
// 错误处理
console.log("发生错误:", error.message);
}
// finally 块
function readFile() {
try {
console.log("打开文件");
// 模拟文件操作
throw new Error("文件读取错误");
} catch (error) {
console.log("处理错误:", error.message);
} finally {
console.log("关闭文件"); // 无论是否发生错误都会执行
}
}
readFile();
// 自定义错误
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
function validateEmail(email) {
if (!email.includes("@")) {
throw new ValidationError("无效的邮箱地址");
}
return true;
}
try {
validateEmail("invalid-email");
} catch (error) {
if (error instanceof ValidationError) {
console.log("验证错误:", error.message);
} else {
console.log("未知错误:", error.message);
}
}
实践练习
👆 请点击上方按钮进行演示操作
选择不同的演示按钮来探索JavaScript的各种功能和用法
练习代码
// 练习1: FizzBuzz 问题
for (let i = 1; i <= 15; i++) {
if (i % 15 === 0) {
console.log("FizzBuzz");
} else if (i % 3 === 0) {
console.log("Fizz");
} else if (i % 5 === 0) {
console.log("Buzz");
} else {
console.log(i);
}
}
// 练习2: 查找数组中的最大值
let numbers = [3, 7, 2, 9, 5];
let max = numbers[0];
for (let i = 1; i < numbers.length; i++) {
if (numbers[i] > max) {
max = numbers[i];
}
}
console.log("最大值: " + max); // 9
// 练习3: 使用 for...of 计算总和
let sum = 0;
for (let num of numbers) {
sum += num;
}
console.log("总和: " + sum); // 26
// 练习4: 使用数组方法
let doubledSum = numbers
.map(num => num * 2)
.reduce((acc, curr) => acc + curr, 0);
console.log("翻倍后的总和: " + doubledSum); // 52
// 练习5: 错误处理实践
function safeDivide(a, b) {
try {
if (b === 0) {
throw new Error("除数不能为零");
}
return a / b;
} catch (error) {
console.log("计算错误:", error.message);
return NaN;
}
}
console.log(safeDivide(10, 2)); // 5
console.log(safeDivide(10, 0)); // NaN (并输出错误信息)
常见问题与解答
1. 什么时候使用 for 循环,什么时候使用 forEach?
for循环更灵活,可以在循环中使用break和continue,也可以访问索引。forEach更简洁,但不能使用break或continue,适合简单的遍历操作。
2. switch 和 if-else 哪个性能更好?
在大多数情况下,性能差异可以忽略不计。选择应该基于代码的可读性:
- 当有多个离散值需要比较时,使用
switch - 当条件涉及范围检查或复杂表达式时,使用
if-else
3. 如何避免回调地狱?
回调地狱是指多层嵌套的回调函数,可以通过以下方式避免:
- 使用 Promise 和 async/await
- 将回调函数提取为命名函数
- 使用控制流库(如 async.js)
最佳实践
- 优先使用
for...of而不是for...in来遍历数组 - 在switch语句中总是包含
default分支 - 避免深层嵌套,使用早期返回减少嵌套层次
- 对于复杂的条件判断,考虑将条件提取为命名函数
- 使用数组迭代方法(
forEach,map,filter)提高代码可读性 - 在循环中避免在条件判断中执行复杂操作
- 使用
try...catch处理可能抛出错误的代码 - 在
finally块中释放资源