古典的な for 文の書き方
「何回繰り返すか」がはっきり決まっているときに使うのが、JavaScript の for 文です。カウンタを使ったループに必要な3つの要素——初期化・継続条件・更新処理——を、1行のヘッダーにまとめて書けるのが特徴です。
5回繰り返して、出力も5行。ヘッダー部分を順番に見ていきましょう。
let i = 0はループが始まる前に1回だけ実行されます。カウンタの初期化ですね。i < 5は毎回のループの前にチェックされます。trueなら本体が動き、falseならループは終了します。i++は本体の処理が終わったあと、次の条件チェックの直前に実行されます。
この3つはカンマではなくセミコロンで区切ります。どれも省略は可能ですが、実際に省くケースは少なく、その場合はたいてい while を使うことになります。
各パーツの動き方を押さえる
順序をしっかり理解するには、一度手で追いかけてみるのが一番です。
順番に追いかけてみましょう。
let i = 1— カウンタ変数を作って 1 を代入。i <= 3を判定 — true なので本体を実行。1を出力。i++を実行 —iは 2 に。i <= 3を判定 — true。2を出力。i++を実行 —iは 3 に。i <= 3を判定 — true。3を出力。i++を実行 —iは 4 に。i <= 3を判定 — false。ループを抜ける。
ポイントは、更新式が本体の 前 ではなく 後 に走るということ。ここで勘違いする人が意外と多いんです。
for文で配列をインデックス指定で回す
for ループの出番として一番多いのが、配列を順番に処理するケースです。このときカウンタ変数がそのままインデックスとして使えます。
いくつか押さえておきたいポイントがあります。
- 配列のインデックスは0から始まります。最初の要素は
0、最後はlength - 1です。 - 条件は
i <= fruits.lengthではなくi < fruits.lengthです。<=にしてしまうと最後を1つ超えてしまい、undefinedが出力されます。 iはletで宣言しているので、スコープはループ内だけ。ループの外では参照できません。
インデックスが不要で値だけ取りたいときは、for...of のほうがシンプルで読みやすいです。これは別のドキュメントで詳しく扱います。
break:途中でループを抜ける
break を使うと、その時点でループを即座に終了できます。目的のものが見つかってそれ以上回す必要がない、というようなケースで便利です。
break が実行された瞬間、制御はループの閉じ括弧の外へ飛び出します。更新式も動かず、条件式も再評価されません。ループはそこで終了です。
continue:今回の繰り返しだけスキップする
continue は現在の繰り返しの残りの処理をスキップして、そのまま更新式へジャンプします。ループ自体は止まらず、今回のパスだけが途中で切り上げられる、というイメージです。
偶数のときは continue が発動して console.log がスキップされ、奇数だけが出力されます。continue は、特定の繰り返しだけ飛ばしたいときに便利で、残りの処理を if の中にネストせずに済むのが嬉しいポイントです。
+1 以外のステップ幅にする
更新式はただの式なので、i++ である必要はありません。たとえば2ずつ増やすこともできます。
Count down:
配列を逆順でループする方法も見ておきましょう。要素を削除しながら処理したいときに便利です。
どんな書き方を選んでも、ルールは同じです。条件式と更新式がうまくかみ合って、最終的に条件が false になるようにしないといけません。そうならないと、ループは永遠に回り続けます。たとえば for (let i = 0; i < 10; i--) は無限ループです。i が逆方向に動いてしまっているからですね。
for文のネスト(入れ子)
for 文は中に別の for 文を入れることもできます。外側のループが1回まわるごとに、内側のループは最後まで_丸ごと_実行されます。
9行の出力になります。外側のループが3回、内側がそれぞれ3回ずつ回る形ですね。カウンタには同じ変数を使い回さずに、row/col や i/j のように意味のわかる名前を付けましょう。
ひとつ注意したいのが、break と continue は一番内側のループにしか効かないという点です。内側のループを抜けても外側は止まりません。外側まで抜けたい場合は、フラグを立てて外側でチェックするか、ネストした処理を関数に切り出して return で抜ける方法を使います。
よくあるハマりどころ
初心者がつまずきやすいポイントをいくつか挙げておきます。
オフバイワンエラー。 i <= arr.length だと1つ行き過ぎ、i < arr.length - 1 だと1つ手前で止まってしまいます。定番は i < arr.length です。
更新処理の書き忘れ。 i++(あるいは相当する更新)を書き忘れると、カウンタが変わらないまま無限ループになります。
for (let i = 0; i < 10; ) {
console.log(i); // 決して終わらない
}
カウンタに var を使う。 var は関数スコープなので、カウンタがループの外まで漏れてしまい、クロージャ絡みで思わぬ挙動を起こすことがあります。素直に let を使いましょう。
ループ中に配列を書き換える。 要素を削除するとインデックスがずれてしまい、結果として次の要素を飛ばしてしまいます。どうしても削除したいときは逆順でループするか、filter で新しい配列を作るのがおすすめです。
他の書き方を選んだほうがいい場面
昔ながらの for 文はいつでも使えますが、よくあるケースではもっと短く書ける選択肢が JavaScript にはあります。
- 配列の値を順に取り出す:
for (const item of array)のほうがすっきりします。 - 配列を変換する:
array.map(fn)で新しい配列が返ります。 - 絞り込む:
array.filter(fn)。 - 合計したり畳み込んだりする:
array.reduce(fn, start)。 - 各要素に対して処理を実行するだけ:
array.forEach(fn)。
インデックスが本当に必要なとき、途中でスキップや break をしたいとき、逆順や 2 つ飛ばしなど変則的なステップを使いたいとき――そんなときこそ昔ながらの for 文の出番です。
次は while ループ
for 文は、回す範囲があらかじめ決まっているときに真価を発揮します。一方で、範囲が決まっていない場合――つまり「ある条件が変わるまで回し続けたい」ときには、while や do...while のほうが向いています。次のページではそれを見ていきましょう。
よくある質問
JavaScriptのfor文の構文は?
カッコの中にセミコロンで区切った3つのパートを書きます: for (初期化; 条件; 更新) { ... }。初期化は最初の1回だけ実行され、条件は毎回のループ前にチェック、更新は各イテレーションの最後に実行されます。典型的な形は for (let i = 0; i < 10; i++) { ... } ですね。
JavaScriptで配列をループ処理するには?
インデックスを使った昔ながらのfor文なら for (let i = 0; i < arr.length; i++) { console.log(arr[i]); } でOK。インデックスが要らないなら for...of のほうがスッキリ書けます: for (const item of arr) { ... }。値の変換やフィルタリングなら、map や filter などの配列メソッドを使うのが一般的です。
for文の中のbreakとcontinueはどう動く?
break はループを即座に抜け、そのまま直後の処理に進みます。continue は今のイテレーションの残りをスキップして更新ステップに飛び、そこから条件をもう一度チェックします。どちらもラベルを使わない限り、一番内側のループにしか効きません。
for文が無限ループになってしまうのはなぜ?
たいていは更新ステップが条件を false に近づけていないのが原因です。たとえば for (let i = 0; i < 10; i--) は、i が0から始まってどんどんマイナスになるので永遠に終わりません。条件と更新がちゃんと噛み合って、最終的に条件がfalseになるか見直してみましょう。