JavaScript 逆引き集 | 配列のループ(for…of)

JavaScript JavaScript
スポンサーリンク

実務向け for…of テンプレート

// 1) 基本(値を直接扱う)
for (const v of arr) {
  // v を処理
}

// 2) インデックスも必要なら entries()
for (const [i, v] of arr.entries()) {
  // i と v を処理
}

// 3) 早期終了(条件成立で break)
for (const v of arr) {
  if (shouldStop(v)) break;
  handle(v);
}

// 4) ネスト削減(continue)
for (const v of arr) {
  if (!isValid(v)) continue;
  process(v);
}

// 5) 非同期処理(逐次 await)
for (const v of arr) {
  await doAsync(v); // バッチ/並列は後述
}

// 6) 反復可能オブジェクト(Map/Set など)
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, val] of map) {
  // key と val を処理
}

const set = new Set([1, 2, 3]);
for (const v of set) {
  // v を処理
}
JavaScript

for…of は配列、文字列、TypedArray、Map/Set、NodeList、ジェネレータなど「反復可能オブジェクト」を自然に走査できる構文です。主要ブラウザで 2015 年以降広く提供されています。


性能とセキュリティのベストプラクティス

  • 値ベースの走査: for…of は値を直接受け取るため、arr[i] のインデックスアクセスより読みやすく、entries() で必要に応じてインデックスも取得します。
  • 破壊的操作を避ける: ループ中に同じ配列へ splice/push を行うと意図しないスキップや重複が起こります。削除は「後ろからの for」か、別配列へ書き出す戦略に切り替えます。
  • 早期終了/ガード: breakcontinue で無駄な反復を抑え、前段で入力検証を済ませてからループに入ります。
  • I/O と並列制御: 逐次 await は簡単ですが遅くなりがち。サイズや API 上限に合わせてバッチ化や並列数制限(例:p-limit)を使い、スループットと安定性を両立します。
  • 巨大入力の防御: 上限件数を設け、サーバーではストリーミング/ページング、クライアントでは仮想リストを検討します。
  • XSS/インジェクション対策: ループで生成した文字列を HTML/SQL に流す場合は必ずエスケープやパラメータ化を行い、テンプレート文字列を生で埋め込まない。
  • UI のキー安定性: React/Vue のリスト表示は宣言的に map/v-for を使い、key は安定した ID を付与します。JSX に直接 for を書くより、前処理と描画を分離するのが一般的です。

React の実務テンプレート

// 1) 宣言的レンダリング(推奨)
function UserList({ users }) {
  return (
    <ul>
      {users.map(u => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

// 2) 重い前処理は for...of、描画は map
function Orders({ orders }) {
  const rows = [];
  for (const o of orders) {
    const total = o.price * o.qty;
    rows.push({
      id: o.id,
      label: `${o.item} x${o.qty}`,
      total,
      expensive: total >= 20000,
    });
  }
  return (
    <section>
      {rows.map(r => (
        <article key={r.id} className={r.expensive ? 'highlight' : ''}>
          <header>{r.label}</header>
          <div{r.total.toLocaleString()}</div>
        </article>
      ))}
    </section>
  );
}

// 3) 非同期バッチ送信(イベントハンドラ)
async function submitBulk(items) {
  const CHUNK = 500;
  for (let i = 0; i < items.length; i += CHUNK) {
    const chunk = items.slice(i, i + CHUNK);
    await fetch('/api/bulk', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(chunk),
    });
  }
}
JSX

React のリスト描画は map が定番。for は JSX 外で前処理に用い、key はインデックスではなく安定 ID を推奨する文脈が広く共有されています。


Vue の実務テンプレート

<template>
  <ul>
    <li v-for="u in viewUsers" :key="u.id">
      {{ u.name }} — {{ u.role ?? 'N/A' }}
    </li>
  </ul>
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({ users: { type: Array, required: true } });

const viewUsers = computed(() => {
  const out = [];
  for (const u of props.users ?? []) {
    out.push({ id: u.id, name: u.name.trim(), role: u.role });
  }
  return out;
});
</script>
HTML
<!-- 大規模リストは仮想スクロール(例: vue-virtual-scroller など) -->
<virtual-list :items="rows" :item-size="48">
  <template #default="{ item }">
    <div :key="item.id">{{ item.label }}</div>
  </template>
</virtual-list>
HTML
  • 前処理は computed: 整形は for…of+computed、UI は v-for で宣言的に。
  • 大量表示: 仮想リスト導入で DOM 負荷を抑えます。

Node.js の実務テンプレート

// 1) for...of + 逐次 await(単純で安全)
export async function processSequential(items, handle) {
  for (const v of items) {
    await handle(v);
  }
}

// 2) チャンク処理(バックプレッシャー対応)
export async function processInChunks(items, handle, size = 1000) {
  for (let i = 0; i < items.length; i += size) {
    const chunk = items.slice(i, i + size);
    for (const v of chunk) {
      await handle(v);
    }
  }
}

// 3) ストリーミング(CSV 行単位)
import fs from 'node:fs';
import readline from 'node:readline';

export async function processCsv(path, onRow) {
  const stream = fs.createReadStream(path, { encoding: 'utf8' });
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
  for await (const line of rl) {
    const cols = line.split(',');
    await onRow(cols);
  }
}
JavaScript
  • 並列制御: 大量 I/O は適切な並列数制限を導入し、API のレート制限や接続上限を守ります。
  • 入力検証: Array.isArray とスキーマチェックで防御。DoS 対策に件数上限・タイムアウトも設定します。

ステップバイステップ具体実装(注文配列の加工・表示・保存)

1. 型と検証

type Order = { id: string; item: string; qty: number; price: number; tags?: string[] };

export function isOrder(x: any): x is Order {
  return x
    && typeof x.id === 'string'
    && typeof x.item === 'string'
    && Number.isFinite(x.qty)
    && Number.isFinite(x.price);
}
TypeScript

2. for…of で前処理(負値防御・整形)

export function preprocessOrders(orders) {
  const out = [];
  for (const o of Array.isArray(orders) ? orders : []) {
    if (!isOrder(o)) continue;
    const qty = Math.max(0, o.qty);
    const price = Math.max(0, o.price);
    const total = qty * price;
    out.push({
      id: o.id,
      label: `${o.item} x${qty}`,
      total,
      expensive: total >= 20000,
      tags: Array.isArray(o.tags) ? o.tags.filter(Boolean) : [],
    });
  }
  return out;
}
JavaScript

3. React で表示(宣言的)

function OrderList({ orders }) {
  const rows = preprocessOrders(orders);
  return (
    <section>
      {rows.map(r => (
        <article key={r.id} className={r.expensive ? 'highlight' : ''}>
          <header>{r.label}</header>
          <div{r.total.toLocaleString()}</div>
          {!!r.tags.length && <small>Tags: {r.tags.join(', ')}</small>}
        </article>
      ))}
    </section>
  );
}
JSX

4. Vue で表示(computed + v-for)

<template>
  <section>
    <article v-for="r in rows" :key="r.id" :class="{ highlight: r.expensive }">
      <header>{{ r.label }}</header>
      <div>¥{{ r.total.toLocaleString() }}</div>
      <small v-if="r.tags?.length">Tags: {{ r.tags.join(', ') }}</small>
    </article>
  </section>
</template>

<script setup>
import { computed } from 'vue';
import { preprocessOrders } from './orders.js';

const props = defineProps({ orders: { type: Array, required: true } });
const rows = computed(() => preprocessOrders(props.orders));
</script>
HTML

5. Node.js で保存(チャンク+トランザクション)

import { preprocessOrders } from './orders.js';

export async function saveOrders(db, orders) {
  const rows = preprocessOrders(orders);
  const CHUNK = 1000;

  for (let i = 0; i < rows.length; i += CHUNK) {
    const chunk = rows.slice(i, i + CHUNK);
    await db.transaction(async tx => {
      for (const r of chunk) {
        await tx.insert({
          id: r.id,
          label: r.label,
          total: r.total,
          tags: r.tags,
        });
      }
    });
  }
}
JavaScript

小さなライブラリ推奨とミニユーティリティ

  • 部分導入: 必要最低限のユーティリティだけ個別インポート(lodash-es のピンポイント import など)でバンドルを軽く保つ。
  • 並列数制限: I/O の並列は p-limit 相当の軽量ツールを採用し、スループットと安定性を両立。
  • 再利用ヘルパ: よく使うパターンを小さな関数に抽出して重複を削減。
// 安全な enumerate(インデックスと値)
export function* enumerate(arr) {
  let i = 0;
  for (const v of arr) yield [i++, v];
}

// findMap(条件成立時に変換して即返す)
export function findMap(arr, pred, map) {
  for (const v of arr) {
    if (pred(v)) return map(v);
  }
  return null;
}

// compactMap(null/undefined を除外して push)
export function compactMap(arr, fn) {
  const out = [];
  for (const v of arr) {
    const r = fn(v);
    if (r != null) out.push(r);
  }
  return out;
}
JavaScript
タイトルとURLをコピーしました