順番に読み進めながら学べます

条件で繰り返す — while / do...while / break / continue

JavaScript の while と do...while を基礎から学びます。回数を先に決められない繰り返しの書き方、無限ループの避け方、continue と break による中断の使い分けを扱います。

空きが出た予約枠に待機列から繰り上げる、残高が足りる間だけ買い足す、といった処理は、終わる条件は決まっていても繰り返す回数を先に数えられません。

この記事では、条件で繰り返す whiledo...while、周を飛ばす continue、ループを抜ける break を扱います。

回数を決めずに繰り返す — while と条件の更新

予約に空きが出たとき、待機列の先頭から順に繰り上げたいとします。1 人繰り上げるたびに空き席が 1 つ減り、席が 0 になった時点で止めます。止めるきっかけは周回数ではなく「空きがある間」という状態なので、件数を数えながら回す for では素直に表せません。

while(丸括弧の条件が成り立つ間、波括弧の本体を繰り返す構文)は、丸括弧に条件だけを書きます。条件は 1 周ごとに、本体へ入る前に判定されます。

初期化はループの前に、条件に使う値の更新は本体の中に、それぞれ自分で書きます。

const waitingList = ["山田", "佐藤", "鈴木"];   // 待機列
let openSeats = 2;                            // 空いている席

// 空き席がある間だけ繰り上げる
while (openSeats > 0) {
  const name = waitingList.shift();
  openSeats--;   // openSeats = openSeats - 1 と同じ意味
  console.log(`${name} さんを繰り上げ(残り ${openSeats} 席)`);
}
// 山田 さんを繰り上げ(残り 1 席)
// 佐藤 さんを繰り上げ(残り 0 席)

console.log(`繰り上げ後の待機: ${waitingList.length} 人`);   // 繰り上げ後の待機: 1 人
終わる while と終わらない while
条件openSeats > 0本体で繰り上げopenSeats--更新あり0 になると条件が不成立条件openSeats > 0本体で繰り上げ更新なし2 のままで終わらない
条件に使う値を本体で減らすと、いつか条件が成り立たなくなって終わります。減らさないと判定が毎回同じ結果になり、本体を実行し続けます

for は更新の式を書く場所が丸括弧の中に決まっていますが、while では本体のどこに書くかを自分で決めます。書く場所を選べる代わりに、更新を書き忘れても文法としては正しいコードのままです。

更新を書き忘れると実行が止まりません

条件が成り立たなくなる周が来なければ、本体は永久に繰り返されます。openSeats-- を書き忘れると、残り席は 2 のままで判定が変わりません。コンソールは 15 秒でタイムアウトを表示しますが繰り返しは止まらず、ページを読み込み直すまで他の演習も動かなくなります。条件に使う値は、本体のどこかで必ず動かします

ポイント残高で同じ商品を買えるだけ買います。balance と unitPrice は宣言済みです。

① 購入した個数を数える変数を 0 で用意してください。

② 残高が 1 個ぶんに足りている間だけ繰り返す条件を書いてください。

③ 1 周ごとに残高を減らして個数を増やし、「◯ 個目(残り ◯ ポイント)」と表示してください。

④ ループのあとに、購入数と残ったポイントを 1 行で表示してください。

(正しく実行できれば解説が表示されます)

JavaScript / TypeScript エディタ

コードを実行してください

先に 1 回実行する — do...while の判定順

通知の送信に失敗したときは、成功するまで何回か送り直します。送信そのものは結果を見る前に 1 回は行う必要がありますが、while は本体に入る前に条件を判定するため、条件に使う変数の初期値しだいでは 1 回も送らずに終わります。

do...while(本体を 1 回実行してから条件を判定する構文)は do { 本体 } while (条件); と書き、末尾のセミコロンまでで 1 つの文です。判定が本体より後に来るので、条件が最初から成り立たない場合でも本体は 1 回実行されます。

const attempts = [false, false, true];   // 送信結果を先に用意した(true が成功)
let sent = true;                         // 前の通知の結果が true のまま残っている
let tried = 0;

// while は先に判定するので、1 回も送らずに終わる
while (!sent && tried < attempts.length) {
  sent = attempts[tried];
  tried++;
}
console.log(`while で送った回数: ${tried}`);   // while で送った回数: 0

// do...while は判定の前に 1 回目を送り、その結果で続けるかを決める
do {
  sent = attempts[tried];
  tried++;
  console.log(`${tried} 回目の送信: ${sent ? "成功" : "失敗"}`);
} while (!sent && tried < attempts.length);
// 1 回目の送信: 失敗 / 2 回目の送信: 失敗 / 3 回目の送信: 成功
while と do...while の入口の違い
sent = trueで始める先に判定!true は false本体に入らない送った回数 0sent = trueで始める先に本体1 回目を送る結果は false!false で続行3 回目の成功で終了
どちらも senttrue のまま始めた結果です。while は判定だけで終わり、do...while は 1 回目を送ってから続けるかを決めます

本体を先に実行するのは 1 周目だけで、2 周目からは while と同じく条件が成り立つ間だけ繰り返します。条件が成り立たないときは 1 回も実行したくないなら while、結果を見る前に 1 回は実行したいなら do...while を選びます。

会議室の予約に、まだ使われていない最小の予約番号を割り当てます。使用済みの番号の配列 usedNumbers は宣言済みです。

① 割り当てる番号を入れる変数を 0 で用意してください。

② 判定より先に本体を実行する繰り返しで、番号を 1 増やし「番号 ◯ を確認」と表示してください。

③ 番号が使用済みの配列に含まれている間は繰り返す条件を書いてください。

④ 繰り返しのあとに「予約番号: ◯」と表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

その周だけ飛ばす — continue の使いどころ

出荷リストを上から処理するとき、在庫が無い行は出荷の処理を飛ばして次の行に進みたくなります。飛ばしたい条件が増えるほど if の波括弧が深くなり、本来やりたい出荷の処理が奥に埋もれていきます。

continue(その周の残りの処理を飛ばして次の周へ進む文)を使うと、飛ばしたい条件に当てはまった時点で周を切り上げられます。ループ自体は続くので、次の周は条件の判定から通常どおり始まります。

const names = ["A5 手帳", "デスクライト", "USB ハブ"];
const stocks = [12, 0, 5];   // 0 は欠品
let index = 0;

while (index < names.length) {
  const name = names[index];
  const stock = stocks[index];
  index++;   // continue で飛ばされないよう先に進めておく

  // 欠品の行は、この周の残りを飛ばして次の周へ
  if (stock === 0) {
    console.log(`${name}: 欠品のためスキップ`);
    continue;
  }
  console.log(`${name}: ${stock} 個を出荷`);
}
// A5 手帳: 12 個を出荷
// デスクライト: 欠品のためスキップ
// USB ハブ: 5 個を出荷
処理する行stock の値と判定その周の結果
1 行目 A5 手帳stock は 12 で 0 ではない12 個を出荷と表示
2 行目 デスクライトstock は 0 なので continue出荷の行は実行されない
3 行目 USB ハブstock は 5 で 0 ではない5 個を出荷と表示

continue が飛ばすのは、その位置から本体の終わりまでの行だけです。continueforfor...of の本体でも使え、for の丸括弧に書いた更新の式は本体の外にあるので、continue した周でも実行されます。

continue は更新の行も飛ばします

index++continue より後ろに書くと、飛ばされた周では index が進みません。次の周も同じ行を読み、stock が 0 のまま条件も変わらないので、繰り返しが終わらなくなります。条件に使う値の更新は continue より前に置きます

レビューの点数から、集計に使えない値を除いた平均を出します。点数の配列 scores は宣言済みです。

① 合計と件数を数える変数を 0 で用意してください。

② for...of で点数を 1 件ずつ取り出してください。

③ 0(未評価)と 5 より大きい値は「◯ を除外」と表示し、その周の残りを飛ばしてください。

④ 残りを合計と件数に足し、ループのあとに「平均: ◯.◯ 点(◯ 件)」と表示してください。

JavaScript / TypeScript エディタ

コードを実行してください

見つかった時点で止める — break の抜け先

ログを上から見て、最初に出たエラーだけを知りたいとします。見つけたあとも最後まで読み続けると処理が無駄になり、書き方によっては後ろにある別のエラーで firstError が上書きされます。

break(実行中のループをその場で終わらせる文)を使うと、条件を満たした周でループを抜けられます。条件分岐の記事で switch を抜けるときに使った文と同じです。見つけた値はループの外で使うので、firstError はループより前に宣言しておきます。

const logs = ["INFO 起動", "WARN 遅延", "ERROR 決済失敗", "ERROR 再送失敗"];
let index = 0;
let firstError = "";

while (index < logs.length) {
  const line = logs[index];
  index++;
  if (line.startsWith("ERROR")) {
    firstError = line;
    break;   // 最初の 1 件が見つかったので、残りは調べない
  }
}

console.log(`最初のエラー: ${firstError}`);   // 最初のエラー: ERROR 決済失敗
console.log(`調べた行数: ${index}`);          // 調べた行数: 3
break が終わらせる波括弧
while の波括弧(ログを 1 行ずつ見る)
  • const line = logs[index] — その周で調べる 1 行
  • break のあとは波括弧を出て、次の console.log の行へ進む
if の波括弧(ERROR で始まる行だけ)
  • firstError = line — 見つけた行を控える
  • break — この波括弧ではなく、外側の while を抜ける
  • この下に行を足しても実行されない
break の下にある本体の行も、残りの周も実行されません。抜けた先は while の波括弧の次の行です

break した周でも、その前に書いた index++ は実行済みです。そのため index は 3 で止まり、ループのあとに表示した「調べた行数: 3」は、見つかった行を 1 から数えた位置と一致します。

continue と break の分かれ目
stock === 0 でcontinueindex < 3 を判定し直すUSB ハブの周へ進む5 個を出荷と表示ERROR で始まる行で breakindex < 4 は判定しない4 行目の ERRORは読まない最初のエラーを表示する行へ
どちらもその周の残りの行は実行しません。continue は条件の判定に戻り、break は判定せずループの外へ出ます

在庫を注文の受付順に引き当て、足りない注文で止めます。注文ごとの個数 orderQuantities と在庫数 stock は宣言済みです。

① for...of で注文の個数を 1 件ずつ取り出してください。

② 個数が在庫より多い周では「◯ 個の注文で在庫不足」と表示し、繰り返しを終わらせてください。

③ 足りる周では在庫を減らし、「◯ 個を引き当て(残り ◯ 個)」と表示してください。

④ ループのあとに「残りの在庫: ◯ 個」と表示してください。

JavaScript / TypeScript エディタ

コードを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1条件が成り立っている状態で while に入り、条件に使う値を本体で一度も変えないとどうなりますか?

Q2条件が最初から成り立たないとき、do...while の本体は何回実行されますか?

Q3ループの中で continue を実行すると、次に何が起きますか?