JavaScript/TypeScript

ループと反復の使い分け

5
  • JavaScript

ループと反復の使い分け — JavaScript Tips

配列を回す手段は複数あります。インデックス操作なら for、値の列挙なら for...of、変換・絞り込みなら map / filter。途中で抜けたいか、非同期を順番に待つかで選ぶところが分かれます。

参考(MDN)


目次

  1. 反復手段の一覧
  2. for ループ — インデックスが必要なとき
  3. for...of — 値を順に処理するとき
  4. forEach — 副作用だけ実行するとき
  5. 配列メソッドとの使い分け
  6. for...in はオブジェクト向け
  7. break と continue
  8. 詰まりやすい点

反復手段の一覧

手段 主な用途 新しい配列を作る break 可能
for インデックス制御・逆順・ステップ 自分で作る
for...of 値の列挙(配列・文字列など) 自分で作る
forEach 各要素への副作用 × ×
map / filter 変換・絞り込み・集計 ○(戻り値) ×
for...in オブジェクトの列挙可能キー ×

「何をしたいか」で選びます。迷ったら次の対応で足ります。

  1. 新しい配列map / filter
  2. 1 件探す・条件判定find / some / every
  3. 合計・グループ化reduce
  4. 副作用だけ(ログ・DOM)forEach または for...of
  5. インデックス操作・breakfor または for...of
  6. オブジェクトのキーfor...in または Object.entries
  7. 順番に awaitfor...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
  • 非同期を順番に awaitfor...offorEach + 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

forfor...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...ofawait、または Promise.all を検討します。

パフォーマンスを気にしすぎて可読性を落とす

小〜中規模では map / filterfor の速度差は体感しにくいことが多いです。読みやすさを優先し、計測でボトルネックが出てから for に寄せてください。

シェア