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

JavaScript JavaScript
スポンサーリンク

実務向け for ループテンプレート

// 1) 基本(length キャッシュで安定)
for (let i = 0, len = arr.length; i < len; i++) {
  const item = arr[i];
  // ...処理
}

// 2) 早期終了(探索など)
for (let i = 0, len = arr.length; i < len; i++) {
  const v = arr[i];
  if (matches(v)) {
    handle(v);
    break;
  }
}

// 3) 後ろから走査(削除・差し替えに強い)
for (let i = arr.length - 1; i >= 0; i--) {
  const v = arr[i];
  if (shouldRemove(v)) arr.splice(i, 1);
}

// 4) continue でネスト削減
for (let i = 0, len = arr.length; i < len; i++) {
  const v = arr[i];
  if (!isValid(v)) continue;
  process(v);
}

// 5) 例外をまとめて処理(ループ内 throw を外で捕捉)
try {
  for (let i = 0, len = arr.length; i < len; i++) {
    mayThrow(arr[i]);
  }
} catch (err) {
  report(err);
}

// 6) for-of(可読性優先の走査)
for (const v of arr) {
  process(v);
}

// 7) インデックスと値が欲しい場合(for-of + entries)
for (const [i, v] of arr.entries()) {
  process(i, v);
}
JavaScript

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

  • 長さのキャッシュ: for (let i = 0, len = arr.length; i < len; i++) のように長さを初期化時にキャッシュする。
  • 破壊的操作の管理: 走査中の splice/push はインデックスを壊しやすい。後ろから走査新配列に書き出しで安全に処理する。
  • 早期終了とガード: 不要な反復を避けるために break/continue を積極活用し、上位で入力検証してからループに入る。
  • メモリアロケーション削減: ホットループで新しい配列やオブジェクトを毎回作らない。必要なら事前に new Array(len) で容量を確保して書き込む。
  • I/O と CPU の分離: ループ内の非同期 I/O はまとめてバッチ化(例: 100〜1000単位)し、CPU処理は同期で一気に回す。
  • 境界・型チェック: 外部入力に由来する配列は Array.isArray と各要素の型を検証。null/undefined を許容するか方針を決める。
  • XSS/インジェクション対策: ループで文字列を生成して UI/DB に渡す場合は必ずエスケープ・パラメータ化を行う。テンプレート文字列をそのまま SQL/HTML に入れない。
  • 巨大入力の防御: 上限件数・総サイズを設定。サーバ側はストリーミング/ページング、クライアント側は仮想リストや段階読み込みを使う。
  • 安定キーの使用(UI): React/Vue のリスト表示で indexkey にしない。並べ替え・挿入で差分が壊れるため、安定 ID を使う。

React の実装テンプレート

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

// 2) 重い前処理は for、描画は map
function Orders({ orders }) {
  const rows = new Array(orders.length);
  for (let i = 0, len = orders.length; i < len; i++) {
    const o = orders[i];
    rows[i] = {
      id: o.id,
      label: `${o.item} x${o.qty}`,
      total: o.price * o.qty,
      expensive: o.price * o.qty >= 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) 非同期バッチ処理(UI イベントで)
async function handleSubmit(items) {
  const CHUNK = 500;
  for (let i = 0, len = items.length; i < len; 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
  • 安定 key: 要素固有の ID を使う。
  • 副作用とレンダリングの分離: 重い処理は useMemo でメモ化、もしくはレンダリング前に for で前処理。

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 src = Array.isArray(props.users) ? props.users : [];
  const out = new Array(src.length);
  for (let i = 0, len = src.length; i < len; i++) {
    const u = src[i];
    out[i] = { 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: 描画は v-for、整形は for+computed に分ける。
  • パフォーマンス: 大規模リストは仮想化を検討。

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

// 1) 大量配列をチャンクで処理(I/O バックプレッシャー対応)
export async function processInChunks(items, handle, size = 1000) {
  for (let i = 0, len = items.length; i < len; i += size) {
    const chunk = items.slice(i, i + size);
    for (let j = 0, jlen = chunk.length; j < jlen; j++) {
      await handle(chunk[j]); // I/O は await、CPU は同期でまとめる
    }
  }
}

// 2) ストリーミング(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);
  }
}

// 3) トランザクションでバルク書き込み
export async function saveBulk(db, rows, chunk = 1000) {
  for (let i = 0, len = rows.length; i < len; i += chunk) {
    const part = rows.slice(i, i + chunk);
    await db.transaction(async tx => {
      for (let j = 0, jlen = part.length; j < jlen; j++) {
        await tx.insert(part[j]);
      }
    });
  }
}
JavaScript
  • 入力検証: Array.isArray と要素のスキーマチェックで防御。
  • DoS 防御: 件数上限・タイムアウト・並列数制限(例: p-limit)を設定。

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

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 で前処理(負値防御・集計)

export function preprocessOrders(orders) {
  const src = Array.isArray(orders) ? orders : [];
  const out = new Array(src.length);
  for (let i = 0, len = src.length; i < len; i++) {
    const o = src[i];
    if (!isOrder(o)) {
      out[i] = null;
      continue;
    }
    const qty = Math.max(0, o.qty);
    const price = Math.max(0, o.price);
    const total = qty * price;
    out[i] = {
      id: o.id,
      label: `${o.item} x${qty}`,
      total,
      expensive: total >= 20000,
      tags: Array.isArray(o.tags) ? o.tags.filter(Boolean) : [],
    };
  }
  return out.filter(Boolean);
}
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 で表示

<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, len = rows.length; i < len; i += CHUNK) {
    const chunk = rows.slice(i, i + CHUNK);
    await db.transaction(async tx => {
      for (let j = 0, jlen = chunk.length; j < jlen; j++) {
        await tx.insert({
          id: chunk[j].id,
          label: chunk[j].label,
          total: chunk[j].total,
          tags: chunk[j].tags,
        });
      }
    });
  }
}
JavaScript

役立つミニユーティリティ

  • 安全列挙(インデックス+値ジェネレータ)
export function* enumerate(arr) {
  for (let i = 0, len = arr.length; i < len; i++) {
    yield [i, arr[i]];
  }
}
JavaScript
  • findMap(条件成立で変換して即返す)
export function findMap(arr, pred, map) {
  for (let i = 0, len = arr.length; i < len; i++) {
    const v = arr[i];
    if (pred(v, i)) return map(v, i);
  }
  return null;
}
JavaScript
  • compactMap(null/undefined を除外)
export function compactMap(arr, fn) {
  const out = [];
  for (let i = 0, len = arr.length; i < len; i++) {
    const v = fn(arr[i], i);
    if (v != null) out.push(v);
  }
  return out;
}
JavaScript
  • 軽量ライブラリの扱い
    • 部分導入: Lodash は必要関数だけ個別インポート(例: import map from 'lodash-es/map')。
    • 代替の小粒: p-limit(並列制御)、tiny-invariant(入力検証)などを用途別に採用。
タイトルとURLをコピーしました