JavaScript 入門

JavaScript ループ制御:break と continue

前章では forwhiledo...while ループ(Loop)について学びました。これらを使用することで、特定のコードを繰り返し実行できるようになります。

しかし、時にはループの挙動をより精密に制御する必要があります。例えば、探し物が予定より早く見つかり、残りの項目をチェックする必要がなくなったらどうすればよいでしょうか? あるいは、特定の条件において、ある回のイテレーション(Iteration)だけは実行したくないが、その後のループ自体は続けたい場合はどうでしょう?

JavaScript は、breakcontinue という2つの特別なステートメント(Statement)を提供しています。これらはループの通常の実行フローを修正し、私たちに精密な制御能力を与えてくれます。

1. break 文:ループを途中で停止させる

break 文は、それを囲んでいる直近の forwhiledo...while、あるいは switch 文を即座に終了させるために使用されます。

ループ内で break に遭遇すると、ループは完全に停止し、プログラムの実行はループの直後に続くステートメントから再開されます。これは、ループにとっての「非常口」のようなものだと考えてください。

1.1 なぜ、いつ 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 が 7 になった時点で if 条件が真になり、foundSeventrue に設定された後、break が実行されます。ループは即座に終了するため、その後の「数字をチェック中: 2」といった処理は行われません。

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; // 1回目の入力をシミュレート
    } else if (attempts === 1) {
        userInput = 0;  // 2回目の入力をシミュレート
    } else {
        userInput = 10; // 3回目の入力をシミュレート
    }

    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.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}個のみ回収。`);
}

2. continue 文:特定のイテレーションをスキップする

break がループを完全に停止させるのに対し、continue 文は異なるアプローチを取ります。

ループ内で continue に遭遇すると、そのイテレーションの残りの部分をスキップし、即座に「次のイテレーション」へとジャンプします。ループ自体は停止せず、次のサイクルに移動するだけです。

2.1 なぜ、いつ 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}`);
}

ここでは、i が偶数の時に continue が実行されます。これにより、その下の console.log("奇数: ${i}") は実行されず、即座に次の増分処理(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() === "") {
        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}"`);
}

2.2.3 示例 3:在庫チェック

店長が在庫を確認しています。「在庫あり」かつ価格が正の数である商品のみをカウントしたいと考えています。「在庫なし」や価格が無効な商品はスキップされます。

const inventoryItems = [
    { name: "ノートPC", 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;
    }

    // 無効な価格(0以下)をスキップ
    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}`);

3. 実践的な例とデモンストレーション

breakcontinue の理解を深めるために、これらを組み合わせたシナリオを見てみましょう。

3.1 示例:試行回数制限付きのクイズシミュレーション

簡単なクイズを構築します。ユーザーは1つの質問に対して3回の回答チャンスがあります。正解すれば次に進みますが、間違えればチャンスを1つ失います。

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 を使用しています。これらの制御フローツールを使いこなすことで、より効率的でロジカルなプログラムを構築できるようになります。