ループと反復の使い分け — JavaScript Tips
配列を回す手段は複数あります。インデックス操作なら for、値の列挙なら for...of、変換・絞り込みなら map / filter。途中で抜けたいか、非同期を順番に待つかで選ぶところが分かれます。
参考(MDN)
目次
- 反復手段の一覧
- for ループ — インデックスが必要なとき
- for...of — 値を順に処理するとき
- forEach — 副作用だけ実行するとき
- 配列メソッドとの使い分け
- for...in はオブジェクト向け
- break と continue
- 詰まりやすい点
反復手段の一覧
| 手段 | 主な用途 | 新しい配列を作る | break 可能 |
|---|---|---|---|
for |
インデックス制御・逆順・ステップ | 自分で作る | ○ |
for...of |
値の列挙(配列・文字列など) | 自分で作る | ○ |
forEach |
各要素への副作用 | × | × |
map / filter 等 |
変換・絞り込み・集計 | ○(戻り値) | × |
for...in |
オブジェクトの列挙可能キー | × | ○ |
「何をしたいか」で選びます。迷ったら次の対応で足ります。
- 新しい配列 →
map/filter - 1 件探す・条件判定 →
find/some/every - 合計・グループ化 →
reduce - 副作用だけ(ログ・DOM) →
forEachまたはfor...of - インデックス操作・break →
forまたはfor...of - オブジェクトのキー →
for...inまたはObject.entries - 順番に await →
for...of
for ループ — インデックスが必要なとき
従来の for は、インデックスを自由に操作できる点が強みです。
const items = ["a", "b", "c", "d"];// 2 つ飛ばしで処理for (let i = 0; i < items.length; i += 2) { console.log(i, items[i]);}// 0 "a"// 2 "c"// 逆順for (let i = items.length - 1; i >= 0; i--) { console.log(items[i]);}隣接要素の比較 や インデックスを別配列に保存 するような処理でも for が向いています。
const values = [3, 7, 2, 9, 5];const peaks = [];for (let i = 1; i < values.length - 1; i++) { if (values[i] > values[i - 1] && values[i] > values[i + 1]) { peaks.push(i); }}console.log(peaks); // [1, 3] — インデックス 1 と 3 が極大for...of — 値を順に処理するとき
for...of は 反復可能(iterable) オブジェクトの値を順に取り出します。配列・文字列・Map・Set などで使えます。
const fruits = ["りんご", "みかん", "ぶどう"];for (const fruit of fruits) { console.log(fruit);}// 文字列も反復可能for (const char of "JS") { console.log(char); // "J", "S"}インデックスも欲しいときは entries() を使います。
for (const [index, fruit] of fruits.entries()) { console.log(`${index}: ${fruit}`);}for...of は読みやすく、await をループ内で使う場合にも向いています(後述)。
const urls = ["/api/a", "/api/b"];async function fetchAll(urls) { const results = []; for (const url of urls) { const res = await fetch(url); results.push(await res.json()); } return results;}forEach — 副作用だけ実行するとき
forEach は配列の各要素に対して関数を 1 回ずつ 呼びます。戻り値は undefined で、ループを途中で抜ける break は使えません。
const logs = ["起動", "接続", "完了"];logs.forEach((message, index) => { console.log(`[${index}] ${message}`);});「DOM にイベントを付ける」「カウンタを更新する」など、結果の配列が不要で副作用だけ のときに使います。
const buttons = document.querySelectorAll("button");buttons.forEach((btn) => { btn.addEventListener("click", () => console.log("clicked"));});return は 現在のコールバックを抜けるだけ で、外側の関数やループ全体は抜けられません(continue 相当の動き)。
[1, 2, 3].forEach((n) => { if (n === 2) return; console.log(n); // 1, 3});配列メソッドとの使い分け
map — 変換して新しい配列
const prices = [100, 200, 300];const taxed = prices.map((p) => p * 1.1);map の中で push などの副作用を書くのは避けましょう。変換なら map、副作用なら forEach / for...of に分けます。
filter — 条件で絞り込み
const users = [ { name: "A", active: true }, { name: "B", active: false },];const activeUsers = users.filter((u) => u.active);reduce — 1 つの値に畳み込み
const total = prices.reduce((sum, p) => sum + p, 0);いつループではなくメソッドか
- 宣言的に「変換・抽出・集計」と読ませたい → メソッド
- 途中で break したい →
for/for...of - 非同期を順番に await →
for...of(forEach+ async は注意が必要)
// NG: forEach は await の完了を待たないurls.forEach(async (url) => { await fetch(url); // 並列に走り出す});// OKfor (const url of urls) { await fetch(url);}for...in はオブジェクト向け
for...in はオブジェクトの 列挙可能なキー(文字列) を列挙します。配列に使うとインデックスが文字列として返り、プロトタイプチェーン上のプロパティも拾う可能性があるため、配列の反復には for...of を推奨 します。
const config = { host: "localhost", port: 3000, debug: true };for (const key in config) { console.log(key, config[key]);}オブジェクトなら Object.keys / Object.entries も選択肢です。
for (const [key, value] of Object.entries(config)) { console.log(key, value);}break と continue
for と for...of では break(ループ脱出)と continue(次の反復へ)が使えます。
const numbers = [1, 2, 3, 4, 5, 6, 7];for (const n of numbers) { if (n % 2 === 0) continue; if (n > 5) break; console.log(n); // 1, 3, 5}some / every / find は配列メソッドながら、真偽判定や検索の意味で「早期終了」に近い動きをします。
const hasNegative = numbers.some((n) => n < 0); // 見つかった時点で打ち切りconst firstBig = numbers.find((n) => n > 3); // 4「条件に合うものを見つけたら終了」は find / some、「全部調べる」は every / for...of と覚えるとよいです。
詰まりやすい点
map なのに副作用だけ書いている
// NGitems.map((item) => { console.log(item);});// OKitems.forEach((item) => console.log(item));forEach で break しようとしている
// NG — break は構文エラー// [1,2,3,4].forEach(n => { if (n === 3) break; });// OKfor (const n of [1, 2, 3, 4]) { if (n === 3) break; console.log(n);}for...in で配列を回している
const arr = ["x", "y", "z"];arr.extra = "oops";for (const key in arr) { console.log(key); // "0", "1", "2", "extra" など紛らわしい}for (const value of arr) { console.log(value); // "x", "y", "z"}非同期 forEach で結果が揃わない
forEach のコールバックを async にしても、forEach 自体は Promise を待ちません。順序保証や完了待ちが必要なら for...of と await、または Promise.all を検討します。
パフォーマンスを気にしすぎて可読性を落とす
小〜中規模では map / filter と for の速度差は体感しにくいことが多いです。読みやすさを優先し、計測でボトルネックが出てから for に寄せてください。