概要
JavaScriptの反復処理において、while 文 と do...while 文は、繰り返し回数が事前に決まっていない場面で使用するループ構文である。
while 文は前判定型ループであり、ループ本体を実行する前に条件を評価する。
条件が最初から偽の場合、ループ本体は1度も実行されない。
一方、do...while 文は後判定型ループであり、ループ本体を必ず1回実行してから条件を評価する。
最低1回の実行が保証されるため、ユーザー入力の検証やメニュー表示等に適している。
break 文はループを途中で終了させ、continue 文は現在の反復をスキップして次の反復へ進む。
これらはネストしたループにおいてラベルと組み合わせて使用することで、外側のループを直接制御することもできる。
while 系ループで最も注意すべき問題は無限ループである。
ループ変数の更新忘れや、continue 使用時の更新スキップが主な原因となる。
安全カウンタやタイムアウト機構を導入することで、無限ループの発生を予防できる。
while文
while 文は、指定した条件が真である間、ループ本体を繰り返し実行する前判定型のループ構文である。
繰り返し回数が事前に不定な処理に適している。
基本構文
while 文の構文は以下の通りである。
while (condition) {
statement
}
while 文は、ループ本体を実行する前に condition (条件式) を評価する。
条件式が真 (truthy) であればループ本体を実行し、再び条件式を評価する。
条件式が偽 (falsy) になった時点でループを終了し、ループ直後のコードへ処理が移る。
条件が最初から偽の場合、ループ本体は1度も実行されない。
let n = 0;
while (n < 3) {
console.log(n);
n++;
}
// 出力: 0, 1, 2
// 条件が最初から偽の場合
let x = 10;
while (x < 5) {
console.log(x); // 一度も実行されない
}
ループ変数の管理
while 文を正しく動作させるには、ループ変数を適切に管理する必要がある。
ループ変数の管理は以下の3段階で構成される。
- 初期化
- ループ変数は
while文の前で初期化する。
- ループ変数は
- 条件式
- ループを継続するかどうかを判定する式をループヘッダに記述する。
- 更新
- ループ本体の最後でループ変数を更新する。更新を忘れると無限ループになる。
let i = 0; // (1) 初期化
while (i < 5) { // (2) 条件式
console.log(i);
i++; // (3) 更新
}
ループ変数の更新をループ本体内のどこに記述するかによって、動作が異なることに注意が必要である。
特に、continue 文と組み合わせる場合は、更新がスキップされないよう慎重に設計する必要がある。
実用的なパターン
while 文が特に有効な実用的なパターンを以下に示す。
条件が不定のループ
取得できるデータが尽きるまで繰り返す処理等、終了条件が事前に決まらない場合に while 文が有効である。
以下の例では、ループ変数 data をループの外で初期化し、ループ本体の末尾で次の値を取得する。
// データがnullになるまで処理を続ける
let data = getNextData();
while (data !== null) {
processData(data);
data = getNextData();
}
カウントダウンループ
初期値から0に向かってカウントダウンするループは、while 文で簡潔に記述できる。
let countdown = 10;
while (countdown > 0) {
console.log(countdown);
countdown--;
}
console.log("発射!");
配列要素の消費
配列から要素を順次取り出して処理するパターンでは、配列が空になったことを条件として使用できる。
const items = [1, 2, 3, 4, 5];
// pop()で末尾から取り出す
while (items.length > 0) {
const item = items.pop();
console.log(item); // 5, 4, 3, 2, 1
}
// shift()で先頭から取り出す
const queue = ["a", "b", "c"];
while (queue.length > 0) {
const item = queue.shift();
console.log(item); // "a", "b", "c"
}
pop() は配列の末尾から要素を取り出し、shift() は先頭から取り出す。
いずれもループ本体の実行ごとに配列の長さが1減少し、最終的に条件が偽になってループが終了する。
do...while文
do...while 文は、ループ本体を最初に1回実行してから条件を評価する後判定型のループ構文である。
条件の真偽に関わらず、ループ本体が必ず1回は実行されることが保証される。
基本構文
do...while 文の構文は以下の通りである。
do {
statement
} while (condition);
while の後の条件式の末尾にはセミコロンが必要である点に注意する。
let result = "";
let i = 0;
do {
i++;
result += i;
} while (i < 5);
console.log(result); // "12345"
この例では、i が 5 になった時点で条件 i < 5 が偽になりループが終了する。
while文との違い
while 文と do...while 文の最大の違いは、条件式を評価するタイミングである。
| 比較項目 | while文 | do...while文 |
|---|---|---|
| 判定タイミング | ループ前 (前判定) | ループ後 (後判定) |
| 最低実行回数 | 0回 | 1回 |
| 初期条件が偽の場合 | 1度も実行されない | 1回実行される |
| 主な用途 | 回数が不定のループ | 最低1回の実行が必要なループ |
| 末尾セミコロン | 不要 | 必要 (; で終わる) |
以下の例で、2つのループの違いを確認できる。
// while文: 条件が偽のため一度も実行されない
let i = 10;
while (i < 5) {
console.log("while:", i); // 出力なし
}
// do...while文: 条件が偽でも1回は実行される
let j = 10;
do {
console.log("do...while:", j); // "do...while: 10"
} while (j < 5);
実用的なパターン
do...while 文が特に有効な実用的なパターンを以下に示す。
入力の検証ループ
ユーザーからの入力を受け取り、その値が有効かどうかを検証するパターンでは、do...while 文が適している。
最初に入力を受け取ってから検証するため、自然なコードフローになる。
let score;
do {
score = parseInt(prompt("0から100のスコアを入力:"));
} while (isNaN(score) || score < 0 || score > 100);
console.log("有効なスコア:", score);
入力が有効な範囲外の場合、プロンプトを繰り返し表示する。
有効な値が入力されて初めてループを抜ける。
メニュー表示ループ
アプリケーションのメニューを表示して選択を受け付けるパターンでは、do...while 文が自然に使用できる。
メニューは最低1回は必ず表示される必要があるため、後判定型が適している。
let choice;
do {
displayMenu();
choice = getUserChoice();
processChoice(choice);
} while (choice !== "exit");
console.log("終了しました");
リトライ処理
ネットワーク通信等で失敗した場合に一定回数まで再試行するパターンでも、do...while 文が使われる。
let success = false;
let retries = 0;
const maxRetries = 3;
do {
try {
const response = await fetch(url);
if (response.ok) {
success = true;
console.log("接続成功");
}
} catch (error) {
retries++;
console.log(`試行 ${retries} 回目失敗:`, error.message);
}
} while (!success && retries < maxRetries);
if (!success) {
console.log("最大試行回数に達しました");
}
最初の試行を必ず1回実行し、失敗した場合のみ再試行する。
成功するか最大試行回数に達した時点でループを抜ける。
break文
break 文は、現在のループ (while、do...while、for)、または switch 文を即座に終了させる制御構文である。
break 文が実行されると、ループの残りの処理をスキップしてループ直後のコードへ処理が移る。
基本的な使用方法
break 文を使用することで、特定の条件が満たされた時点でループを終了させることができる。
// 目的の値が見つかったらループを抜ける
const numbers = [1, 3, 5, 8, 9, 11];
let found = -1;
let i = 0;
while (i < numbers.length) {
if (numbers[i] % 2 === 0) {
found = numbers[i];
break; // 最初の偶数が見つかった時点でループを終了
}
i++;
}
console.log("最初の偶数:", found); // 8
break 文はネストしたループの内側にある場合、最も内側のループのみを終了させる。
外側のループは継続される。
for (let i = 0; i < 3; i++) {
let j = 0;
while (j < 3) {
if (j === 1) break; // 内側のwhileループを終了
console.log(i, j);
j++;
}
// 外側のforループは継続される
}
// 出力: 0 0, 1 0, 2 0
ラベル付きbreak
ネストしたループで外側のループを直接終了させる場合、ラベル付きbreak を使用する。
ラベルはループの直前にコロンを付けて定義する。
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
const target = 5;
let foundRow = -1;
let foundCol = -1;
outerLoop: for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] === target) {
foundRow = i;
foundCol = j;
break outerLoop; // 外側のforループを終了
}
}
}
console.log(`found at [${foundRow}][${foundCol}]`); // found at [1][1]
ラベル付きbreak は、2次元配列の検索や複雑なネスト構造のループからの脱出に使用される。
ただし、ラベルの多用はソースコードの可読性を下げるため、使用は必要最小限に留めることが推奨される。
switchとbreak
break 文は、switch 文の各caseの末尾にも使用する。
switch 文では break を省略すると次のcaseに処理が流れるフォールスルーが発生するため、意図的なフォールスルーでない限り各caseには break を記述する。
switch 文の詳細については、JavaScriptの基礎 - 条件分岐のページを参照すること。
const day = "Monday";
switch (day) {
case "Monday":
console.log("月曜日");
break; // break がなければ次の case へフォールスルー
case "Tuesday":
console.log("火曜日");
break;
default:
console.log("その他の曜日");
break;
}
continue文
continue 文は、現在の反復処理をスキップして次の反復へ進む制御構文である。
break 文がループ全体を終了させるのに対し、continue 文は現在の反復のみをスキップする。
基本的な使い方
continue 文を使用することにより、特定の条件を満たす要素をスキップしながらループを継続できる。
// 偶数のみを出力する
let i = 0;
while (i < 10) {
i++;
if (i % 2 !== 0) continue; // 奇数をスキップ
console.log(i); // 2, 4, 6, 8, 10
}
forループでのcontinue
for ループで continue を使用する場合、更新式 (カウンタのインクリメント) は continue 後も必ず実行される。
これは while ループとの重要な違いである。
for (let i = 0; i < 10; i++) {
if (i === 3) continue; // i++ は continue の後も実行される
console.log(i); // 3以外の 0, 1, 2, 4, 5, 6, 7, 8, 9 が出力
}
for ループでは、continue が実行された後、ループヘッダの更新式 (i++) が実行されてから次の条件判定が行われる。
このため、無限ループになる危険性はない。
whileループでのcontinue
while ループで continue を使用する場合は、ループ変数の更新に注意が必要である。
continue の後にループ変数の更新コードがある場合、その更新がスキップされて無限ループになる危険性がある。
// 危険なパターン: 無限ループになる
let i = 0;
while (i < 5) {
if (i === 3) continue; // i の更新がスキップされる!
i++; // i === 3 のとき、この行は実行されない
}
// 安全なパターン: 更新をcontinueの前に移動
let j = 0;
while (j < 5) {
j++; // 先に更新してからスキップ判定
if (j === 3) continue;
console.log(j); // 1, 2, 4, 5
}
// もう1つの安全なパターン: continue の前に更新を記述
let k = 0;
while (k < 5) {
if (k === 3) {
k++; // スキップ前に更新する
continue;
}
console.log(k);
k++;
}
while ループで continue を使用する場合は、ループ変数の更新が必ず実行されるよう設計する必要がある。
ラベル付きcontinue
ネストしたループで外側のループの次の反復へジャンプする場合、ラベル付きcontinue を使用する。
const matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// 特定の行をスキップする
outerLoop: for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
if (matrix[i][j] === 5) {
continue outerLoop; // 外側のループの次の反復へジャンプ
}
console.log(matrix[i][j]);
}
}
// 出力: 1, 2, 3, 4 (5が含まれる行の残り 6 がスキップされる), 7, 8, 9
ラベル付きcontinue により、内側のループで特定の条件を満たした時点で外側のループの次の反復に進むことができる。
無限ループの回避
無限ループとは、終了条件が満たされることなく永久に繰り返されるループのことである。
while 系ループではループ変数の管理を自分で行う必要があるため、無限ループが発生しやすい。
無限ループの原因
無限ループが発生する主な原因を以下に示す。
- ループ変数の更新忘れ
- ループ本体でループ変数を更新しないと、条件式が常に真のまま変化しない。
// 無限ループの例: i が更新されない let i = 0; while (i < 10) { console.log(i); // i++ を忘れている }
- continueによる更新スキップ
continueの後にループ変数の更新があると、スキップされて無限ループになる。// 無限ループの例: i === 3 のとき i++ がスキップされる let i = 0; while (i < 5) { if (i === 3) continue; i++; }
- 終了条件が到達不可能
- ループ変数は更新されているが、条件式が偽になる値に到達できない場合も無限ループになる。
// 無限ループの例: i が減少しているが条件は増加方向 let i = 10; while (i < 100) { console.log(i); i--; // i は減少しているので条件 i < 100 は常に真 }
- 外部状態に依存した条件の変化忘れ
- 外部関数が返す値や外部変数に依存した条件で、その状態が変化しない場合も無限ループになる。
予防策
無限ループを防ぐための予防策を以下に示す。
安全カウンタの導入
ループに安全カウンタを設けることにより、一定回数以上の反復を強制的に停止できる。
let iterations = 0;
const maxIterations = 10000;
while (condition && iterations < maxIterations) {
iterations++;
// 処理
}
if (iterations >= maxIterations) {
console.warn("最大反復回数に達しました。ループを強制終了します。");
}
タイムアウト機構の導入
時間ベースの終了条件を設けることにより、処理時間が長くなりすぎることを防ぐ。
const startTime = Date.now();
const timeout = 5000; // 5秒
while (condition) {
if (Date.now() - startTime > timeout) {
console.warn("タイムアウト: ループを強制終了します。");
break;
}
// 処理
}
ループ変数の管理を明確にする
ループ変数の初期化、条件式、更新を一目で確認できるよう、ソースコードを整理する。
更新漏れを防ぐために、更新処理はループ本体の先頭か末尾に統一して配置することが推奨される。
// 推奨: 更新をループ本体の末尾に統一
let i = 0; // (1) 初期化
while (i < limit) { // (2) 条件式
// 処理
i++; // (3) 更新 (常に最後)
}
無限ループの検出と対処
実行中に無限ループを検出した場合の対処方法を以下に示す。
- Webブラウザでの対処
- [Ctrl] + [C]キー または [Ctrl] + [W]キーでスクリプトを停止する。
- Webブラウザのタブを強制終了する。
- デベロッパーツールのSourcesパネルで一時停止ボタンを使用する。
- Node.jsでの対処
- [Ctrl] + [C]キーでプロセスを停止する。
- 別のターミナルから
killコマンドでプロセスを終了する。
- デバッグ方法
- ループの反復回数をカウントするカウンタ変数を追加する。
console.logでループ変数の変化を追跡する。- ブレークポイントを設定してステップ実行で状態を確認する。
// デバッグ用カウンタを追加した例
let i = 0;
let debugCount = 0;
while (someCondition) {
debugCount++;
console.log(`反復 ${debugCount}: i = ${i}`);
if (debugCount > 1000) {
console.error("無限ループの可能性を検出");
break;
}
// ...処理
}
while系ループとfor系ループの使い分け
JavaScriptには複数のループ構文が用意されており、それぞれ適した用途がある。
ループ構文を適切に選択することで、ソースコードの可読性と保守性が向上する。
| ループ | 初期化 | 条件判定 | 更新 | 最低実行回数 | 最適な用途 |
|---|---|---|---|---|---|
| while | ループ外 | ループ前 | ループ内 | 0回 | 繰り返し回数が不定なループ |
| do...while | ループ外 | ループ後 | ループ内 | 1回 | 最低1回の実行が必要なループ |
| for | ループ式内 | ループ式内 | ループ式内 | 0回 | 繰り返し回数が決まっているループ |
| for...of | 不要 (自動) | 自動 | 自動 | 0回 | イテラブルの値を順に処理するループ |
各ループ構文の選択基準を以下に示す。
| ループ構文 | 選択基準 |
|---|---|
| while文 | 繰り返し回数が事前に不定な場合 特定の条件が変化するまで繰り返す場合 データを順次取得しながら処理する場合 |
| do...while文 | ループ本体を最低1回は必ず実行する必要がある場合 ユーザ入力の検証 メニュー表示 リトライ処理 |
| for文 | 繰り返し回数が事前にわかっている場合 インデックスを使った配列へのアクセスが必要な場合 複数のカウンタ変数を同時に制御する場合 |
| for...of文 | 配列 文字列 Map、Set等のイテラブルを順に処理する場合 インデックスが不要で値のみを扱う場合 |
for...of の詳細については、JavaScriptの基礎 - 反復処理(for文)のページを参照すること。
関連情報
- JavaScriptの基礎 - 変数宣言
- let / const / varの宣言方法、スコープ、ホイスティング
- JavaScriptの基礎 - プリミティブ型
- 7つのプリミティブ型、typeof演算子、型の自動変換
- JavaScriptの基礎 - 比較演算子と論理演算子
- === / ==の違い、短絡評価、Null合体演算子、オプショナルチェーン
- JavaScriptの基礎 - 反復処理(for文)
- for文、for...of、for...in、使い分け