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

JavaScript JavaScript
スポンサーリンク

実務向け forEach テンプレート

// 1) 基本(値・インデックス・配列の3引数)
arr.forEach((v, i, a) => {
  // v: 要素, i: インデックス, a: 配列(元の参照)
});

// 2) this を使いたい(第2引数 thisArg)
const ctx = { rate: 1.1 };
prices.forEach(function (p) {
  this.rate; // 1.1
}, ctx);

// 3) 早期終了が不要な一括処理(ログ、累積、副作用)
logs.forEach(e => logger.info(e));

// 4) 入力検証(null/undefined 防御)
(arr ?? []).forEach(v => process(v));

// 5) 例外集約(ループは継続、失敗だけ蓄積)
const errors = [];
items.forEach(v => {
  try {
    handle(v);
  } catch (e) {
    errors.push({ v, e });
  }
});
// エラーまとめて扱う
if (errors.length) report(errors);
JavaScript

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

  • 用途の適材適所:
    • 副作用目的(ログ、DOM 操作、外部 I/O)なら forEach は読みやすい。
    • 変換やフィルタmap/filter/reduce を使うと意図が明確で中間配列の管理も不要。
  • 早期終了不可:
    • forEach は break/continue/return で反復を止められない。探索や条件成立後に終了したいなら for/for...of/some/every/find を選ぶ。
  • 非同期の落とし穴:
    • await arr.forEach(...) は待機しない。逐次処理は for...of、並列は Promise.all+分割や並列数制御を使う。
  • 破壊的操作を避ける:
    • 走査中に splice/push で同じ配列を変更すると意図しない挙動や性能劣化。必要なら別の出力配列へ書き出す。
  • 疎配列と境界:
    • new Array(10) のような疎配列は空要素をスキップする。期待通りに回したいなら初期化してから使う。
  • 入力検証とエスケープ:
    • 外部入力由来の配列は型チェック、文字列を UI/DB/HTML に渡すなら必ずエスケープやパラメータ化でインジェクション対策。
  • 大規模入力対策:
    • 巨大配列はチャンク処理・ストリーミング・仮想リストでメモリと描画の負荷を分散。
  • メモリ割り当て削減:
    • ホットパスではクロージャの過剰生成を避け、必要なら外で関数を定義して再利用。

React の実務テンプレート

// 1) リスト表示は map(宣言的)
function UserList({ users }) {
  return (
    <ul>
      {users.map(u => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

// 2) 副作用前処理は forEach、描画は map
function Orders({ orders }) {
  const rows = [];
  orders.forEach(o => {
    const total = Math.max(0, o.qty) * Math.max(0, o.price);
    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) {
  // 逐次処理は for...of を推奨(forEach は await できない)
  for (const item of items) {
    await fetch('/api/item', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(item),
    });
  }
}
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 = [];
  (props.users ?? []).forEach(u => {
    out.push({ id: u.id, name: u.name.trim(), role: u.role });
  });
  return out;
});
</script>
HTML
  • computed 前処理: 整形は forEach でも可、UI は v-for
  • 大量表示: 仮想スクロール(virtual list)導入を検討。

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

// 1) 逐次 I/O は for...of(forEach + 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) 例外を集約しつつ継続
export async function safeProcess(items, handle) {
  const errors = [];
  items.forEach(v => {
    try {
      handle(v);
    } catch (e) {
      errors.push({ v, e });
    }
  });
  return errors;
}
JavaScript
  • 並列制御: レート制限や接続上限を守るため、後述のミニライブラリで制御。
  • 入力検証: Array.isArray とスキーマチェックで防御。

ステップバイステップ具体実装(注文配列の整形/表示/保存)

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. forEach で前処理(負値防御・整形)

export function preprocessOrders(orders) {
  const out = [];
  (Array.isArray(orders) ? orders : []).forEach(o => {
    if (!isOrder(o)) return;
    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

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

  • 並列数制御の導入:
    • p-limit 相当の軽量実装で I/O の同時実行を制御(例:5〜10並列)。
  • 部分導入の方針:
    • lodash-es を個別 import(例:import chunk from 'lodash-es/chunk')でバンドルを軽量化。
  • 再利用ヘルパの抽出:
    • よく使うパターンを小さな関数に切り出し、重複を排除。
// 1) 簡易 p-limit(軽量並列制御)
export function createLimiter(limit = 5) {
  const queue = [];
  let active = 0;

  const run = async fn => {
    active++;
    try { return await fn(); }
    finally {
      active--;
      if (queue.length) queue.shift()();
    }
  };

  return fn =>
    active < limit
      ? run(fn)
      : new Promise(res => queue.push(() => res(run(fn))));
}

// 使い方
const limit = createLimiter(5);
await Promise.all(items.map(item => limit(() => doAsync(item))));

// 2) safeForEach(例外を集約)
export function safeForEach(arr, fn) {
  const errors = [];
  (arr ?? []).forEach((v, i) => {
    try { fn(v, i); } catch (e) { errors.push({ i, v, e }); }
  });
  return errors;
}

// 3) toRows(整形テンプレ)
export function toRows(orders) {
  const out = [];
  (orders ?? []).forEach(o => {
    if (!isOrder(o)) return;
    const t = Math.max(0, o.qty) * Math.max(0, o.price);
    out.push({ id: o.id, label: `${o.item} x${o.qty}`, total: t });
  });
  return out;
}
JavaScript

直感的な選び方の指針

  • 副作用中心なら forEach。
  • 変換・抽出なら map/filter/reduce。
  • 早期終了・逐次 async が必要なら for/for…of。
タイトルとURLをコピーしました