流程控制

掌握JavaScript中的条件判断和循环控制

流程控制概述

流程控制语句用于控制代码的执行顺序,主要包括:

  • 条件语句 - 根据条件执行不同的代码块
  • 循环语句 - 重复执行代码块
  • 跳转语句 - 改变代码的正常执行流程
  • 错误处理语句 - 处理程序执行过程中的错误

程序执行流程

JavaScript程序默认按照从上到下的顺序执行,但可以通过流程控制语句改变这种顺序:

  1. 顺序执行 - 默认的执行方式
  2. 条件分支 - 根据条件选择执行路径
  3. 循环执行 - 重复执行某段代码
  4. 异常处理 - 处理执行过程中的错误

条件语句

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循环更灵活,可以在循环中使用breakcontinue,也可以访问索引。forEach更简洁,但不能使用breakcontinue,适合简单的遍历操作。

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块中释放资源

下一步学习

掌握了流程控制后,接下来可以学习: