JavaScript | 基礎構文:ループ – ループの最適化

JavaScript JavaScript
スポンサーリンク

ループの最適化とは何か

ループの最適化とは、「同じ結果を保ったまま、ループの中で行う仕事量を減らす」工夫のことです。1回の処理が少し重くても、ループで何千・何万回と繰り返されると全体の速度に大きく響きます。ポイントは「ループの外に出せるものは外へ」「早く終われるなら早く終わる」「より速いデータ構造を選ぶ」の3つです。


無駄な処理を減らす基本テクニック

ループ外に出せる計算は外へ

毎回同じ値になる計算は、ループの外で一度だけ計算しましょう。ループごとの重さが確実に減ります。

// 悪い例:同じ計算を毎回している
const items = [1, 2, 3, 4, 5];
for (let i = 0; i < items.length; i++) {
  const taxRate = Number(process.env.TAX_RATE) || 0.1; // 毎回同じ
  items[i] = items[i] * (1 + taxRate);
}

// 良い例:外に出す
const items2 = [1, 2, 3, 4, 5];
const taxRate2 = Number(process.env.TAX_RATE) || 0.1;
for (let i = 0; i < items2.length; i++) {
  items2[i] = items2[i] * (1 + taxRate2);
}
JavaScript

長さや定数をキャッシュする

配列の長さや不変の値を変数にとっておくと、毎回のプロパティ参照を避けられます。小さな差でも、回数が多いと効きます。

const arr = new Array(100000).fill(0);

// length をキャッシュ
for (let i = 0, len = arr.length; i < len; i++) {
  arr[i] += 1;
}
JavaScript

条件分岐の順序を工夫する(早期スキップ・終了)

よく起きる条件を先にチェックし、当てはまったら continuebreak で早く抜けます。高頻度のケースほど前に置くと合計の仕事量が減ります。

// 早期スキップ(continue)
for (const user of users) {
  if (!user.active) continue;       // 非アクティブはすぐ飛ばす
  if (user.role !== "member") continue;
  process(user);                     // 条件を通過したものだけ処理
}

// 早期終了(break)
for (let i = 0; i < logs.length; i++) {
  if (logs[i].level === "fatal") {
    alertAdmin(logs[i]);
    break;                           // 最初の致命的エラーで終了
  }
}
JavaScript

データ構造で高速化する

配列探索をセットで置き換える

「含まれているかどうか」を何度も判定するなら、配列より Set が高速です。配列の includes は最悪 O(n)、Sethas は平均 O(1) です。

// 配列:毎回走査する
const bannedList = ["foo", "bar", "baz"];
function isBannedByArray(x) {
  return bannedList.includes(x);
}

// Set:即座に判定
const bannedSet = new Set(["foo", "bar", "baz"]);
function isBannedBySet(x) {
  return bannedSet.has(x);
}
JavaScript

検索を辞書(オブジェクト・Map)に

ID から素早くレコードを引くなら、Map やオブジェクトで索引を作ると速くなります。毎回配列を探すより効率的です。

// 悪い例:毎回 find(O(n))
function findByIdSlow(list, id) {
  return list.find(item => item.id === id);
}

// 良い例:索引(インデックス)を事前作成(O(1))
const index = new Map();
for (const item of list) {
  index.set(item.id, item);
}
function findByIdFast(id) {
  return index.get(id);
}
JavaScript

ループの書き方を見直す

for, for…of, forEach の選び方

パフォーマンス・可読性・必要な機能で選びます。一般にインデックスが必要なら for、要素だけで十分なら for...of がバランス良い選択です。forEach はコールバック呼び出しが増えるため、超大量データではわずかに不利になることがあります。

// インデックスが欲しい:for
for (let i = 0; i < arr.length; i++) {
  /* arr[i] を使う */
}

// 要素だけで良い:for...of(読みやすい)
for (const item of arr) {
  /* item を使う */
}

// 手軽だが関数呼び出しが増える:forEach
arr.forEach(item => { /* ... */ });
JavaScript

ネストを減らす

二重三重のループは処理量が爆発しがちです。可能なら条件の前処理や索引化でネストを浅くし、組み合わせ数を減らします。

// 悪い例:二重ループで毎回比較
for (const a of listA) {
  for (const b of listB) {
    if (a.id === b.id) join(a, b);
  }
}

// 良い例:索引化で一発取得
const indexB = new Map(listB.map(b => [b.id, b]));
for (const a of listA) {
  const b = indexB.get(a.id);
  if (b) join(a, b);
}
JavaScript

入出力や副作用をまとめる

DOM 操作やログはまとめて

ループ内で DOM を何度も触る、ログを大量に出す、ネットワーク呼び出しを繰り返すのは大きな遅延の原因になります。まず計算を集約し、最後に一括で反映しましょう。

// 悪い例:毎回 DOM を更新
for (const msg of messages) {
  const li = document.createElement("li");
  li.textContent = msg;
  list.appendChild(li);              // たびたびレイアウトが発生
}

// 良い例:まとめて挿入
const frag = document.createDocumentFragment();
for (const msg of messages) {
  const li = document.createElement("li");
  li.textContent = msg;
  frag.appendChild(li);
}
list.appendChild(frag);              // 一度だけ反映
JavaScript

実用例で理解する

不要な計算を外に出して高速化

// タグの正規化が高コストだと仮定
function normalize(tag) { return tag.trim().toLowerCase(); }

const posts = [/* ...多数... */];
const targetTag = normalize(" JavaScript ");

let count = 0;
// 悪い:毎回 normalize(" JavaScript ") を呼ぶ
for (const post of posts) {
  if (post.tags.map(normalize).includes(targetTag)) count++;
}

// 良い:外に出す + Set で会員判定
let count2 = 0;
for (const post of posts) {
  const tagSet = new Set(post.tags.map(normalize));
  if (tagSet.has(targetTag)) count2++;
}
JavaScript

早期終了で総コストを削減

// 最初に条件を満たすユーザーを探す
function findActiveAdmin(users) {
  for (const user of users) {
    if (!user.active) continue;       // 非アクティブなら即スキップ
    if (user.role === "admin") return user; // 見つかったら即終了
  }
  return null;
}
JavaScript

まとめ

ループ最適化のコツは「外に出せる計算は外へ」「よく起きる条件から早くスキップ・終了」「より速いデータ構造に置き換え」「ネストを浅く」「副作用をまとめる」の5つです。難しいテクニックより、まずこの基本を徹底するだけで体感できる改善が得られます。繰り返しが重い箇所ほど、効果は大きくなります。

タイトルとURLをコピーしました