Javascript 零基础教程

JavaScript 循环控制:break 和 continue

上一章中,我们学习了 forwhiledo...while 循环,它们允许我们多次执行一段代码。

然而,有时我们需要对循环的行为进行更精细的控制。如果你正在寻找某样东西并且提前找到了,不再需要检查剩余的项目怎么办?或者,如果某个条件使得循环的某次特定迭代变得无关紧要,但你仍然希望循环继续执行后续的迭代怎么办?

JavaScript 提供了两个特殊的语句——breakcontinue——赋予了我们这种精确的控制力,允许我们修改循环的正常执行流程。

1. break 语句:提前停止循环

break 语句用于立即终止最近的包围它的 forwhiledo...whileswitch 语句。

当在循环内部遇到 break 时,循环完全停止,程序执行将继续从循环后的下一条语句开始。你可以把它想象成循环的紧急出口

1.1 为什么以及何时使用 break

使用 break 的主要原因是在循环内部满足了特定条件,使得进一步的迭代变得不必要或适得其反。这可以通过防止无用的计算来显著提高代码的效率。

使用 break 的常见场景:

  • 搜索项目: 如果你正在遍历一个列表(比如数组)来查找特定值,一旦找到该值,就没有必要继续搜索了。break 允许你立即退出循环。
  • 处理特定条件: 如果发生错误,或达到了目标阈值,break 可以停止原本会继续的循环。
  • 退出“无限”循环: 有时,我们会故意使用 while(true) 循环,而 break 提供了基于循环体内部条件(如用户输入或处理的数据计数)退出该循环的机制。

1.2 break 的详细示例

让我们看一些实际例子来了解 break 的作用。

1.2.1 示例 1:查找第一次出现

想象你有一列数字,想看看数字 7 是否存在。一旦找到它,就不需要检查剩余的数字了。

const numbers = [1, 5, 3, 9, 7, 2, 8, 4];
let foundSeven = false;

// 遍历数组中的每个数字
for (let i = 0; i < numbers.length; i++) {
    const currentNumber = numbers[i];
    console.log(`正在检查数字: ${currentNumber}`);

    // 检查当前数字是否为 7
    if (currentNumber === 7) {
        console.log("找到了数字 7!");
        foundSeven = true;
        break; // 立即退出循环
    }
}

if (foundSeven) {
    console.log("搜索结束: 找到了 7。");
} else {
    console.log("搜索结束: 没有找到 7。");
}

// 预期输出:
// 正在检查数字: 1
// 正在检查数字: 5
// 正在检查数字: 3
// 正在检查数字: 9
// 正在检查数字: 7
// 找到了数字 7!
// 搜索结束: 找到了 7。

在这个例子中,一旦 currentNumber 变成 7if 条件为真,foundSeven 被设置为 true,然后执行 break。循环立即终止,"正在检查数字: 2" 等后续内容永远不会被打印。这演示了 break 如何防止不必要的迭代。

1.2.2 示例 2:带有限制的用户输入验证

假设我们要让用户输入一个正数,但我们只给他们最多 3 次尝试机会。

let attempts = 0;
const maxAttempts = 3;
let userInput = 0; // 初始化为一个无效值

while (attempts < maxAttempts) {
    // 在真实的浏览器中,你会使用 prompt()
    // 为了演示,我们将模拟用户输入
    // 尝试 1: -5 (无效)
    // 尝试 2: 0 (无效)
    // 尝试 3: 10 (有效)
    
    if (attempts === 0) {
        userInput = -5; // 模拟第一次输入
    } else if (attempts === 1) {
        userInput = 0;  // 模拟第二次输入
    } else {
        userInput = 10; // 模拟第三次输入
    }

    console.log(`尝试第 ${attempts + 1} 次: 用户输入了 ${userInput}`);

    if (userInput > 0) {
        console.log(`收到有效输入: ${userInput}`);
        break; // 因为给出了有效输入,退出循环
    } else {
        console.log("无效输入。请输入一个正数。");
    }

    attempts++;
}

if (userInput > 0) {
    console.log("程序继续使用有效输入。");
} else {
    console.log("达到最大尝试次数。无法获取有效输入。");
}

// 预期输出 (模拟输入):
// 尝试第 1 次: 用户输入了 -5
// 无效输入。请输入一个正数。
// 尝试第 2 次: 用户输入了 0
// 无效输入。请输入一个正数。
// 尝试第 3 次: 用户输入了 10
// 收到有效输入: 10
// 程序继续使用有效输入。

在这里,即使 maxAttempts 是 3,如果在第一次或第二次尝试时输入了有效的正数,break 语句也会提前停止 while 循环,防止用户用完所有尝试次数。

1.2.3 示例 3:假设场景 - 机器人清洁任务

想象一个简单的机器人在打扫房间。它需要在电池耗尽或轮班结束前捡起 5 个特定的“灰尘团”。如果它找到了所有 5 个,它应该立即停止。

let dustBunniesCollected = 0;
const targetBunnies = 5;
const totalSpotsInRoom = 10; // 机器人将检查的地点数量

for (let spot = 1; spot <= totalSpotsInRoom; spot++) {
    console.log(`机器人正在检查地点 ${spot}...`);

    // 模拟在某些地点发现灰尘团
    if (spot === 2 || spot === 4 || spot === 6 || spot === 7 || spot === 9) {
        dustBunniesCollected++;
        console.log(`  发现一个灰尘团!总共收集了: ${dustBunniesCollected}`);
    }

    // 检查机器人是否收集了足够的灰尘团
    if (dustBunniesCollected === targetBunnies) {
        console.log("机器人已收集所有目标灰尘团!");
        break; // 任务完成,退出循环
    }
}

if (dustBunniesCollected === targetBunnies) {
    console.log("任务完成!机器人返回基地。");
} else {
    console.log(`任务未完成。机器人收集了 ${targetBunnies} 个中的 ${dustBunniesCollected} 个。`);
}

// 预期输出:
// ... (前略)
// 机器人正在检查地点 9...
//   发现一个灰尘团!总共收集了: 5
// 机器人已收集所有目标灰尘团!
// 任务完成!机器人返回基地。

在这个场景中,机器人不需要检查地点 10,即使它还有电量和时间,因为它的主要目标(收集 5 个灰尘团)已经达成。break 语句确保它一旦达到目标就停止搜索。

2. continue 语句:跳过一次迭代

虽然 break 完全停止循环,但 continue 语句采取了不同的方式。

当在循环内部遇到 continue 时,它会跳过循环当前迭代的剩余部分,并立即跳转到下一次迭代。循环不会停止;它只是移动到下一个周期。

2.1 为什么以及何时使用 continue

当你想针对特定条件绕过循环代码的某些部分,而不停止循环处理后续迭代时,continue 语句非常有用。

2.1.1 使用 continue 的常见场景:

  • 过滤数据: 你可能正在处理一个项目列表,但有些项目无效或不符合某些标准。continue 允许你跳过处理这些特定项目,移动到下一个有效项目。
  • 跳过特定情况: 如果循环内的某个计算或操作仅针对特定类型的数据,continue 可以有效地绕过不需要类型的代码。
  • 优化处理: 当你知道某个特定的迭代不需要进一步操作时,continue 可以防止执行该迭代中不必要的代码。

2.2 continue 的详细示例

让我们探索如何使用 continue 来跳过循环的部分内容。

2.2.1 示例 1:只打印奇数

假设你想打印从 1 到 10 的所有数字,但你只对奇数感兴趣。

console.log("打印 1 到 10 中的奇数:");

for (let i = 1; i <= 10; i++) {
    // 检查当前数字是否为偶数
    if (i % 2 === 0) {
        // 如果是偶数,跳过本次迭代的剩余部分,进入下一个数字
        console.log(`  跳过偶数: ${i}`);
        continue;
    }
    // 这段代码只有在数字是奇数时才会运行
    console.log(`奇数: ${i}`);
}

// 预期输出:
// 打印 1 到 10 中的奇数:
// 奇数: 1
//   跳过偶数: 2
// 奇数: 3
// ...

这里,当 i 是偶数时,if (i % 2 === 0) 条件为真,执行 continue。这会立即跳转到下一次迭代(即 i++),跳过该偶数的 console.log("奇数: ${i}") 语句。

2.2.2 示例 2:处理有效数据条目

想象你在处理一列用户评论。有些评论可能是空的,或者包含不当词汇。你想跳过这些,只处理有效的评论。

const comments = [
    "文章写得好!",
    "", // 空评论
    "这很有用。",
    "spam spam spam", // 不当内容 (模拟)
    "谢谢分享。"
];
const inappropriateWords = ["spam", "badword", "offensive"];

console.log("正在处理评论:");

for (let i = 0; i < comments.length; i++) {
    const comment = comments[i];

    // 检查空评论
    if (comment.trim() === "") { // .trim() 移除首尾空格
        console.log(`  跳过索引 ${i} 的空评论`);
        continue; // 跳到下一条评论
    }

    // 检查不当词汇
    let hasInappropriateContent = false;
    for (const word of inappropriateWords) { 
        if (comment.toLowerCase().includes(word)) {
            hasInappropriateContent = true;
            break; // 发现不当词汇,无需为*这条*评论检查其他词汇
        }
    }

    if (hasInappropriateContent) {
        console.log(`  跳过索引 ${i} 的不当评论: "${comment}"`);
        continue; // 跳到下一条评论
    }

    // 如果运行到这里,说明评论是有效的,可以处理
    console.log(`正在处理有效评论: "${comment}"`);
}

// 预期输出:
// 正在处理评论:
// 正在处理有效评论: "文章写得好!"
//   跳过索引 1 的空评论
// 正在处理有效评论: "这很有用。"
//   跳过索引 3 的不当评论: "spam spam spam"
// 正在处理有效评论: "谢谢分享。"

在这个场景中,continue 帮助我们有效地过滤掉了不应处理的评论。循环仍然检查每条评论,但对于特定的评论,它会跳过“处理有效评论”的步骤。

2.2.3 示例 3:假设场景 - 库存检查

商店经理正在检查库存。他们只想统计“有货”且价格大于 0 的商品。“缺货”或价格无效的商品应被跳过。

const inventoryItems = [
    { name: "笔记本电脑", status: "有货", price: 1200 },
    { name: "鼠标", status: "有货", price: 25 },
    { name: "键盘", status: "缺货", price: 75 },
    { name: "显示器", status: "有货", price: 300 },
    { name: "摄像头", status: "有货", price: -10 }, // 无效价格
    { name: "耳机", status: "缺货", price: 50 }
];

let totalValidItemsCount = 0;
let totalValidItemsValue = 0;

console.log("正在进行库存检查:");

for (let i = 0; i < inventoryItems.length; i++) {
    const item = inventoryItems[i];

    // 跳过“缺货”的商品
    if (item.status === "缺货") {
        console.log(`  跳过 "${item.name}" - 缺货。`);
        continue;
    }

    // 跳过价格无效(非正数)的商品
    if (item.price <= 0) {
        console.log(`  跳过 "${item.name}" - 价格无效。`);
        continue;
    }

    // 如果运行到这里,说明商品是有效的
    console.log(`  处理有效商品: "${item.name}" (价格: $${item.price})`);
    totalValidItemsCount++;
    totalValidItemsValue += item.price;
}

console.log("\n库存检查完成!");
console.log(`找到有效商品总数: ${totalValidItemsCount}`);
console.log(`有效商品总价值: $${totalValidItemsValue}`);

这个例子展示了如何在单个循环中多次使用 continue 来基于不同标准过滤项目,确保只处理相关数据进行计算。

3. 实践示例与演示

让我们在一个稍微复杂的场景中结合我们对 breakcontinue 的理解。

3.1 示例:模拟带有尝试次数的简单测验

想象构建一个简单的测验。用户有 3 次机会回答一个问题。如果他们答对了,我们继续;否则,他们失去一次机会。

const correctAnswer = "paris";
let score = 0;
const maxAttemptsPerQuestion = 3;

console.log("欢迎参加地理测验!");
console.log("问题: 法国的首都是哪里?");

let attemptsRemaining = maxAttemptsPerQuestion;
let answeredCorrectly = false;

while (attemptsRemaining > 0) {
    // 模拟用户输入以进行演示
    let userAnswer;
    if (attemptsRemaining === 3) {
        userAnswer = "london"; // 错误
    } else if (attemptsRemaining === 2) {
        userAnswer = "berlin"; // 错误
    } else {
        userAnswer = "paris"; // 正确
    }

    console.log(`\n你的猜测 (尝试 ${maxAttemptsPerQuestion - attemptsRemaining + 1}/${maxAttemptsPerQuestion}): ${userAnswer}`);

    if (userAnswer.toLowerCase() === correctAnswer) {
        console.log("正确!你答对了!");
        score++;
        answeredCorrectly = true;
        break; // 因为问题已正确回答,退出循环
    } else {
        attemptsRemaining--;
        if (attemptsRemaining > 0) {
            console.log(`不正确。你还剩 ${attemptsRemaining} 次尝试机会。`);
        } else {
            console.log("不正确。你的尝试机会已用完。");
        }
    }
}
// ... (后续逻辑)

这个组合示例展示了用于在达成目标(正确答案)时停止流程的 break