目录

第三章 控制流

3.1 程序控制流概述

程序控制流决定了代码执行的顺序。默认情况下,JavaScript代码从上到下顺序执行,但通过控制流语句,我们可以:

控制流语句分为三类:

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穿透示例(有时是有意的)
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:

然后优化这个函数,使其可以接收任意数量的规则(数字和对应的字符串)。

参考答案

练习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的控制流语句:

关键要点:

下一章将学习函数,这是JavaScript中最重要的概念之一。