WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

【実測】JavaScriptの配列処理はどれが速い?forEach/map/for文をベンチした

生徒

forEachとmapって何が違うんですか?速さも違うんですか?

ペン博士

いい質問だ。速さも用途もちゃんと違う。実際に計測したデータを見せながら説明しよう。

JavaScriptで配列を扱うとき、for文・forEachmapreducefor...ofwhileなど、さまざまな手法が存在する。「どれを使えばいいか」は書き方の好みだけでなく、パフォーマンス・可読性・意図の明確さという3軸で選ぶべきだ。この記事では、実際にNode.js環境で手元計測した数値を示しながら、各手法の特性と使い分けを徹底解説する。


目次

検証環境と計測方法

まずベンチマークの前提を明示する。計測結果はあくまで手元環境での目安であり、実行環境・JSエンジンのバージョン・データの内容によって変動する。

項目
OSmacOS 14.5 (Apple M2)
Node.jsv22.2.0
計測方法performance.now() で前後を挟み、10回の平均を取る
配列サイズ100万要素(1,000,000件)の数値配列
ウォームアップ計測前に同一処理を3回空回し(JITウォームアップ)

以下が今回使ったベンチマークスクリプトの全体だ。コピーして手元で動かせる。

// benchmark.js  Node.js v22で動作確認済み
const { performance } = require('perf_hooks');

const SIZE = 1_000_000;
const arr  = Array.from({ length: SIZE }, (_, i) => i + 1);

function bench(label, fn, warmup = 3, runs = 10) {
  // ウォームアップ(JIT最適化を安定させる)
  for (let w = 0; w < warmup; w++) fn();

  const times = [];
  for (let r = 0; r < runs; r++) {
    const t0 = performance.now();
    fn();
    times.push(performance.now() - t0);
  }

  const avg = times.reduce((a, b) => a + b, 0) / times.length;
  const min = Math.min(...times);
  const max = Math.max(...times);
  console.log(
    `${label.padEnd(20)} avg=${avg.toFixed(2)}ms  min=${min.toFixed(2)}ms  max=${max.toFixed(2)}ms`
  );
  return avg;
}

// ─── 各手法の実装 ───────────────────────────────────────────────────

// 1) for(伝統的なインデックスループ)
bench('for', () => {
  let sum = 0;
  for (let i = 0; i < arr.length; i++) {
    sum += arr[i];
  }
  return sum;
});

// 2) while
bench('while', () => {
  let sum = 0;
  let i   = arr.length;
  while (i--) {
    sum += arr[i];
  }
  return sum;
});

// 3) for...of
bench('for...of', () => {
  let sum = 0;
  for (const v of arr) {
    sum += v;
  }
  return sum;
});

// 4) forEach
bench('forEach', () => {
  let sum = 0;
  arr.forEach(v => { sum += v; });
  return sum;
});

// 5) reduce
bench('reduce', () => arr.reduce((acc, v) => acc + v, 0));

// 6) map(副作用目的での誤用を計測)
bench('map (×副作用)', () => {
  let sum = 0;
  arr.map(v => { sum += v; }); // 本来の用途ではない使い方
  return sum;
});

このスクリプトをNode.jsで実行すると、各手法の平均・最小・最大実行時間が出力される。以下に手元での実測結果をまとめた。


実測ベンチマーク結果

手元環境(上記のM2 Mac + Node.js v22.2.0)で計測した結果が以下だ。数値は10回実行の平均ミリ秒。

手法平均実行時間(ms)最速比(for基準)戻り値可読性主な用途
for(インデックス)2.11.00×(基準)なし汎用・高速処理
while(逆順)2.00.95×なし最速を狙う場面
for…of3.41.62×なし可読性重視の繰り返し
forEach4.82.29×なし(undefined)副作用ありの繰り返し
reduce3.21.52×集計値中〜高合計・変換・グループ化
map(副作用目的)5.32.52×新配列(不要)誤用→使うべきでない

最速はwhileのインデックス逆順ループ」という結果だった。ただし差は微々たるもので、100万件の処理でも2〜5ms程度の差にすぎない。普通のWebアプリ開発では速度差よりも可読性・意図の明確さを優先すべきだ。


for文(インデックスループ)

基本構文と特徴

for文はJavaScriptで最も基本的なループ構文だ。インデックスを直接操作できるため、途中でbreakする・特定インデックスだけ処理する・逆順ループなど、細かい制御が必要な場面で強い。

// 基本的なfor文
const fruits = ['apple', 'banana', 'cherry'];

for (let i = 0; i < fruits.length; i++) {
  console.log(`${i}: ${fruits[i]}`);
}
// 0: apple
// 1: banana
// 2: cherry

// 逆順ループ
for (let i = fruits.length - 1; i >= 0; i--) {
  console.log(fruits[i]);
}
// cherry → banana → apple

// 特定インデックスだけスキップ
for (let i = 0; i < fruits.length; i++) {
  if (i === 1) continue; // banana をスキップ
  console.log(fruits[i]);
}
// apple, cherry

// 途中でbreak
for (let i = 0; i < fruits.length; i++) {
  if (fruits[i] === 'banana') break;
  console.log(fruits[i]);
}
// apple だけ出力

パフォーマンス最適化のコツ

わずかでも速度を稼ぎたいなら、arr.lengthをループ外でキャッシュするテクニックがある。ただし現代のV8エンジンはほぼ自動最適化するため、劇的な差は出ない。

// length をキャッシュ(古典的最適化)
const arr = new Array(1_000_000).fill(1);
const len = arr.length; // ← キャッシュ

let sum = 0;
for (let i = 0; i < len; i++) {
  sum += arr[i];
}

// 現代のNode.jsでは上記とほぼ同速だが、
// 明示的に書くとコードの意図が明確になる利点はある

while文

基本構文と逆順ループ

while文は条件が真の間ループし続ける。逆順ループ(デクリメント)と組み合わせると、i--が0になった瞬間にfalsyになる性質を利用でき、若干高速になる。

// 通常のwhileループ
const nums = [10, 20, 30, 40, 50];
let i = 0;
while (i < nums.length) {
  console.log(nums[i]);
  i++;
}

// 逆順while(ベンチで最速だったパターン)
let j = nums.length;
let sum = 0;
while (j--) {
  sum += nums[j];
}
console.log(sum); // 150

// do...while(最低1回は実行される)
let count = 0;
do {
  console.log(`count: ${count}`);
  count++;
} while (count < 3);
// count: 0
// count: 1
// count: 2

whileは可読性が低くなりやすい。速度よりも可読性が重要なアプリコードでは、forやfor…ofを使うほうが保守しやすい。whileはパフォーマンスクリティカルな処理(ゲームエンジン・音声処理など)で意識的に使うくらいにとどめよう。


for…of文

イテラブルに対して使う現代的な書き方

for...ofはES2015で追加された構文で、配列だけでなく、Mapや Set・文字列・NodeListなど、イテラブルなオブジェクト全般に使える。インデックスが不要な場合はfor文よりも意図が明確になる。

// 配列のfor...of
const colors = ['red', 'green', 'blue'];
for (const color of colors) {
  console.log(color);
}

// entriesでインデックスも取得
for (const [index, color] of colors.entries()) {
  console.log(`${index}: ${color}`);
}
// 0: red, 1: green, 2: blue

// Setに対して
const set = new Set([1, 2, 3, 2, 1]);
for (const v of set) {
  console.log(v); // 1, 2, 3(重複除去済み)
}

// Mapに対して
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, val] of map) {
  console.log(`${key}=${val}`);
}

// 文字列に対して(文字単位で処理)
for (const char of 'hello') {
  console.log(char); // h, e, l, l, o
}

// breakも使える
for (const num of [1, 2, 3, 4, 5]) {
  if (num === 3) break;
  console.log(num); // 1, 2
}

for…in との違い(重要)

for…in は配列に使ってはいけないfor...inはオブジェクトの列挙可能なプロパティ名(キー)をイテレートするもので、配列に使うとプロトタイプチェーン上のプロパティも拾ってしまうリスクがある。

// for...in はオブジェクトのキー列挙に使う
const person = { name: 'Alice', age: 25, city: 'Tokyo' };
for (const key in person) {
  console.log(`${key}: ${person[key]}`);
}
// name: Alice
// age: 25
// city: Tokyo

// ❌ 配列にfor...inを使う(やってはいけない)
const arr = [10, 20, 30];
Array.prototype.customMethod = function() {}; // プロトタイプ拡張の例

for (const key in arr) {
  console.log(key); // "0", "1", "2", "customMethod" ← 意図しないキーが出る!
}

// ✅ 配列にはfor...ofを使う
for (const val of arr) {
  console.log(val); // 10, 20, 30(正常)
}

forEach

コールバック型の繰り返し処理

forEachはES5から使える配列メソッドで、コールバック関数を各要素に対して実行する。戻り値は常にundefinedであり、breakで途中終了はできない。副作用(DOM操作・ログ出力・外部状態の変更)を伴う処理に適している。

// 基本的なforEach
const scores = [85, 92, 78, 95, 88];

scores.forEach((score, index, array) => {
  // 第2引数: インデックス
  // 第3引数: 元の配列
  console.log(`${index + 1}位: ${score}点`);
});

// DOM操作の例(ブラウザ環境)
const buttons = document.querySelectorAll('.btn');
buttons.forEach(btn => {
  btn.addEventListener('click', () => {
    btn.classList.toggle('active');
  });
});

// オブジェクト配列の処理
const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob',   age: 30 },
];
const names = [];
users.forEach(user => {
  names.push(user.name); // 副作用: 外部配列に追加
});
console.log(names); // ['Alice', 'Bob']
// ※この場合はmapを使うほうが意図が明確

// ⚠️ breakはできない(例外を使うハックがあるが非推奨)
scores.forEach(score => {
  if (score > 90) return; // returnはcontinueと同義(ループは止まらない)
  console.log(score); // 85, 78, 88 が出力される
});

forEach で非同期処理を使う落とし穴

forEachの中でawaitは効かない。これはJavaScript開発者がよくハマる落とし穴の一つだ。

// ❌ forEach + async/await は正しく動かない
const ids = [1, 2, 3];

// これは「並列実行」になってしまい、完了を待てない
ids.forEach(async (id) => {
  const data = await fetch(`/api/user/${id}`);
  console.log(await data.json());
});
console.log('done'); // ← 全fetchが終わる前に出力される!

// ✅ for...of + await が正しい(直列実行)
async function fetchSequential() {
  for (const id of ids) {
    const data = await fetch(`/api/user/${id}`);
    console.log(await data.json()); // 1件ずつ順番に処理
  }
  console.log('done'); // ← 全件処理後に出力
}

// ✅ Promise.all + map で並列実行(高速)
async function fetchParallel() {
  const results = await Promise.all(
    ids.map(id => fetch(`/api/user/${id}`).then(r => r.json()))
  );
  console.log(results); // 全件まとめて取得
}

map

配列を変換して新しい配列を作る

mapは配列の各要素を変換し、同じ長さの新しい配列を返す。元の配列は変更されない(イミュータブル)。データの変換・加工に使う。

// 数値を2倍に変換
const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(n => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(nums);    // [1, 2, 3, 4, 5] ← 元は変わらない

// オブジェクト配列からプロパティを抽出
const users = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob',   age: 30 },
  { id: 3, name: 'Carol', age: 22 },
];
const names = users.map(u => u.name);
console.log(names); // ['Alice', 'Bob', 'Carol']

// オブジェクトを別の形に変換(データ整形)
const formatted = users.map(u => ({
  label: `${u.name}(${u.age}歳)`,
  value: u.id,
}));
// [{ label: 'Alice(25歳)', value: 1 }, ...]

// インデックスを使った変換
const withRank = users.map((u, i) => ({
  rank: i + 1,
  ...u,
}));
// [{ rank: 1, id: 1, name: 'Alice', ... }, ...]

// Reactでのリストレンダリング(よく使うパターン)
// const elements = items.map(item => <li key={item.id}>{item.name}</li>);

mapを副作用目的に使う誤用

ベンチでも最も遅かった「mapを副作用目的に使うパターン」は、パフォーマンスと可読性の両面で問題がある。

// ❌ mapを副作用に使う(よくある誤用)
const results = [];
[1, 2, 3].map(n => {
  results.push(n * 2); // 副作用! 戻り値(新配列)を使っていない
});
// mapが生成した配列は捨てられているので無駄

// ✅ 副作用 → forEach を使う
[1, 2, 3].forEach(n => {
  results.push(n * 2);
});

// ✅ 変換 → mapの戻り値を使う
const doubled = [1, 2, 3].map(n => n * 2);

// なぜ誤用がいけないか
// 1. mapは新配列を生成するのでメモリ無駄
// 2. コードを読む人が「戻り値を使っているはず」と思って混乱する
// 3. ベンチでもforEachより遅い

reduce

配列をひとつの値に畳み込む

reduceは配列を左から右へ処理し、単一の値(数値・オブジェクト・配列など何でも)に集約する最も汎用的な高階関数だ。慣れるまで難しく見えるが、合計・グループ化・フラット化など幅広い処理を表現できる。

// 合計を求める(基本)
const nums = [1, 2, 3, 4, 5];
const sum = nums.reduce((accumulator, current) => accumulator + current, 0);
console.log(sum); // 15

// 引数の意味:
// accumulator: 前の計算結果(初回は初期値=0)
// current    : 現在の要素
// 0          : 初期値

// 最大値を求める
const max = nums.reduce((acc, v) => (v > acc ? v : acc), -Infinity);
console.log(max); // 5

// 配列をオブジェクトに変換(グループ化)
const people = [
  { name: 'Alice', dept: 'dev' },
  { name: 'Bob',   dept: 'design' },
  { name: 'Carol', dept: 'dev' },
];
const grouped = people.reduce((acc, person) => {
  const key = person.dept;
  if (!acc[key]) acc[key] = [];
  acc[key].push(person.name);
  return acc;
}, {});
console.log(grouped);
// { dev: ['Alice', 'Carol'], design: ['Bob'] }

// フラット化(flat()が使えないケース向け)
const nested = [[1, 2], [3, 4], [5]];
const flat = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flat); // [1, 2, 3, 4, 5]

// 単語の出現回数を数える
const words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple'];
const counts = words.reduce((acc, word) => {
  acc[word] = (acc[word] || 0) + 1;
  return acc;
}, {});
console.log(counts);
// { apple: 3, banana: 2, cherry: 1 }

reduceRight

reduceRightは右から左へ処理する。関数の合成(pipe/compose)でよく使われる。

// reduceRight: 右から左へ
const arr = ['a', 'b', 'c'];
const result = arr.reduceRight((acc, v) => acc + v, '');
console.log(result); // 'cba'

// 関数合成(compose)の実装
const double = x => x * 2;
const addOne = x => x + 1;
const square = x => x * x;

// compose: 右から左へ関数を適用
const compose = (...fns) => x => fns.reduceRight((v, fn) => fn(v), x);
const transform = compose(double, addOne, square);
console.log(transform(3)); // square(3)=9 → addOne(9)=10 → double(10)=20

filter と find も合わせて知っておく

filter: 条件を満たす要素だけ抽出

filterは条件を満たす要素だけを集めた新しい配列を返す。元の配列は変わらない。

const products = [
  { name: 'MacBook',  price: 200000, inStock: true  },
  { name: 'iPhone',   price: 130000, inStock: false },
  { name: 'iPad',     price:  80000, inStock: true  },
  { name: 'AirPods',  price:  28000, inStock: true  },
];

// 在庫ありの商品だけ
const available = products.filter(p => p.inStock);
console.log(available.length); // 3

// 価格が10万円以下の在庫あり商品
const affordable = products.filter(p => p.inStock && p.price <= 100000);
// [{ name: 'iPad', ... }, { name: 'AirPods', ... }]

// map と filter のチェーン
const names = products
  .filter(p => p.inStock)
  .map(p => p.name);
console.log(names); // ['MacBook', 'iPad', 'AirPods']

find と findIndex

findは条件を満たす最初の要素を返す。見つからなければundefinedfindIndexはそのインデックスを返す。

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob'   },
  { id: 3, name: 'Carol' },
];

// IDで検索
const user = users.find(u => u.id === 2);
console.log(user); // { id: 2, name: 'Bob' }

// インデックスで検索
const idx = users.findIndex(u => u.name === 'Carol');
console.log(idx); // 2

// 見つからない場合
const notFound = users.find(u => u.id === 99);
console.log(notFound); // undefined

// findIndex が -1 を返す場合
const notFoundIdx = users.findIndex(u => u.id === 99);
console.log(notFoundIdx); // -1

手法の使い分けチートシート

どの手法を使うか迷ったら、以下の判断フローを参考にしよう。

やりたいこと使う手法理由
すべての要素を処理(副作用あり)forEach戻り値不要・意図が明確
配列を別の値に変換map新配列を返す・元は不変
条件で絞り込むfilter条件一致のみ新配列
ひとつの値に集約reduce合計・グループ化など
最初の一致を探すfind / findIndex全件スキャン不要・高速
高速な繰り返し(速度優先)for / whileオーバーヘッドが最小
イテラブル全般(Map・Set等)for…of汎用性が高い
非同期処理を順番に実行for…of + awaitforEachではawaitが効かない
非同期処理を並列実行Promise.all + map全件同時に走らせて高速

また、複数の操作をチェーンする場合はfilter → map → reduceの順で考えると整理しやすい。

// 実践的なチェーン例:
// 「在庫ありの商品の価格合計を求める」
const products = [
  { name: 'A', price: 1000, inStock: true  },
  { name: 'B', price: 2000, inStock: false },
  { name: 'C', price: 1500, inStock: true  },
  { name: 'D', price: 3000, inStock: true  },
];

const totalAvailable = products
  .filter(p => p.inStock)           // 在庫ありに絞る
  .map(p => p.price)                // 価格だけ取り出す
  .reduce((sum, price) => sum + price, 0); // 合計

console.log(totalAvailable); // 5500

// 大量データならreduce一本にまとめた方が高速(一度だけ走査)
const totalFast = products.reduce((sum, p) => {
  return p.inStock ? sum + p.price : sum;
}, 0);
console.log(totalFast); // 5500(同じ結果)

よくある誤用パターンと対策

誤用1: mapを副作用だけに使う

先述の通り、mapの戻り値を無視するのはメモリの無駄で、コードの可読性も下げる。副作用が目的なら必ずforEachを使おう。

誤用2: forEachで非同期処理を待てないと思って変な実装をする

// ❌ よくある「とりあえずPromise.allをforEachに入れる」誤り
const ids = [1, 2, 3];

// これは動くが、直列か並列かが不明瞭
const promises = [];
ids.forEach(id => {
  promises.push(fetch(`/api/${id}`));
});
await Promise.all(promises); // 動くが、mapを使えばシンプル

// ✅ シンプルな書き方
const results = await Promise.all(ids.map(id => fetch(`/api/${id}`)));

誤用3: reduceで単純な処理を複雑に書く

// ❌ 配列の合計をreduceで書く(冗長ではないが…)
const sum = [1, 2, 3].reduce((a, b) => a + b);
// これは問題ないが、下記の方が読みやすい場面もある

// ✅ 単純な合計ならfor...ofやforEachも明確
let total = 0;
for (const n of [1, 2, 3]) total += n;

// reduceが本領を発揮するのは「集約の形が複雑な時」
// 例:ネストしたデータのフラット化、グループ化、
//     複数の計算を同時に行う場合など

誤用4: 巨大配列へのchainedメソッド乱用

100万件を超えるような配列に対してfilter → map → reduceをチェーンすると、配列を3回走査することになる。大量データはreduceひとつにまとめるか、for文で一度に処理するほうが効率的だ。

const LARGE = new Array(1_000_000).fill(0).map((_, i) => ({
  value: i,
  active: i % 2 === 0,
}));

// ❌ 100万件を3回走査(非効率)
const result1 = LARGE
  .filter(x => x.active)
  .map(x => x.value * 2)
  .reduce((s, v) => s + v, 0);

// ✅ 100万件を1回だけ走査(高効率)
const result2 = LARGE.reduce((s, x) => {
  if (x.active) s += x.value * 2;
  return s;
}, 0);

console.log(result1 === result2); // true(同じ結果)

実践:パフォーマンスを意識した実装パターン集

大量データのフィルタリングと変換を効率よく行う

実際のアプリで10万件以上のデータを扱う場面では、チェーンの書き方一つでパフォーマンスが大きく変わる。以下に実測を交えたパターンを示す。

// 100,000件のユーザーデータから「アクティブで30歳未満」を抽出して名前リストを作る

const users = Array.from({ length: 100_000 }, (_, i) => ({
  id: i,
  name: `User${i}`,
  age: Math.floor(Math.random() * 60) + 18,
  active: Math.random() > 0.3,
}));

// パターン1: filter → map チェーン(2回走査)
console.time('chain');
const result1 = users
  .filter(u => u.active && u.age < 30)
  .map(u => u.name);
console.timeEnd('chain');
// 手元計測: 約 8.2ms

// パターン2: reduce 一本(1回走査)
console.time('reduce');
const result2 = users.reduce((acc, u) => {
  if (u.active && u.age < 30) acc.push(u.name);
  return acc;
}, []);
console.timeEnd('reduce');
// 手元計測: 約 5.4ms(約34%高速)

// パターン3: for文(最速)
console.time('for');
const result3 = [];
for (let i = 0; i < users.length; i++) {
  const u = users[i];
  if (u.active && u.age < 30) result3.push(u.name);
}
console.timeEnd('for');
// 手元計測: 約 4.1ms(チェーンの約2倍速)

// ※ 件数が少ない場合(1,000件程度)は差は無視できるほど小さい

配列の重複削除パターン比較

SetとArray.fromの組み合わせが最もシンプルだが、条件付き重複削除(IDベースなど)にはreduceやMapが有効だ。

// シンプルな重複削除(プリミティブ値)
const nums = [1, 2, 3, 2, 1, 4, 3];

// パターン1: Set(最もシンプル・高速)
const unique1 = [...new Set(nums)];
// [1, 2, 3, 4]

// パターン2: filter + indexOf(古典的・遅い)
const unique2 = nums.filter((v, i) => nums.indexOf(v) === i);
// [1, 2, 3, 4] ← 同じ結果だが O(n²) で遅い

// オブジェクト配列のID重複削除
const items = [
  { id: 1, name: 'A' },
  { id: 2, name: 'B' },
  { id: 1, name: 'A duplicate' }, // id:1 の重複
  { id: 3, name: 'C' },
];

// パターン1: Map を使った重複削除(高速・O(n))
const uniqueById = [...new Map(items.map(item => [item.id, item])).values()];
// [{ id:1, name:'A' }, { id:2, name:'B' }, { id:3, name:'C' }]

// パターン2: reduce で重複削除
const uniqueByIdReduce = items.reduce((acc, item) => {
  if (!acc.find(x => x.id === item.id)) acc.push(item);
  return acc;
}, []);
// ※ find が内側でO(n)なので全体O(n²)。Mapの方が速い

配列のソートと安定ソート

JavaScriptのArray.prototype.sortはES2019以降安定ソート(stable sort)が保証された。ソートの書き方と注意点を示す。

// 数値ソート(比較関数が必要)
const nums = [10, 1, 21, 2];

// ❌ デフォルトのsortは文字列順(バグの元)
console.log([...nums].sort());
// [1, 10, 2, 21] ← 文字列として比較!

// ✅ 比較関数を渡す
console.log([...nums].sort((a, b) => a - b)); // 昇順
// [1, 2, 10, 21]
console.log([...nums].sort((a, b) => b - a)); // 降順
// [21, 10, 2, 1]

// オブジェクト配列のソート
const users = [
  { name: 'Carol', age: 22 },
  { name: 'Alice', age: 30 },
  { name: 'Bob',   age: 22 },
];

// ageで昇順ソート(同じageはname順で安定ソート)
users.sort((a, b) => {
  if (a.age !== b.age) return a.age - b.age;
  return a.name.localeCompare(b.name); // 文字列比較
});
// Carol(22) → Bob(22) → Alice(30) ではなく
// Bob(22) → Carol(22) → Alice(30) になる(name昇順)

// 日本語文字列のソート(localeCompare必須)
const items = ['東京', 'あいう', 'ABC', '大阪'];
items.sort((a, b) => a.localeCompare(b, 'ja'));

配列の分割・結合パターン

// 配列を特定サイズのチャンクに分割
function chunkArray(arr, size) {
  const chunks = [];
  for (let i = 0; i < arr.length; i += size) {
    chunks.push(arr.slice(i, i + size));
  }
  return chunks;
}
console.log(chunkArray([1,2,3,4,5,6,7], 3));
// [[1,2,3], [4,5,6], [7]]

// reduceでも書ける
const chunk = (arr, n) =>
  arr.reduce((acc, _, i) =>
    i % n === 0 ? [...acc, arr.slice(i, i + n)] : acc, []);

// 2次元配列をフラット化
const matrix = [[1,2],[3,4],[5,6]];
console.log(matrix.flat());        // [1,2,3,4,5,6]
console.log(matrix.flatMap(x=>x)); // 同じ結果(map+flatを一度に)

// 配列のzip(2配列を組み合わせる)
const keys   = ['a', 'b', 'c'];
const values = [1, 2, 3];
const zipped = keys.map((k, i) => [k, values[i]]);
// [['a',1], ['b',2], ['c',3]]

// Object.fromEntries でオブジェクトに変換
const obj = Object.fromEntries(zipped);
// { a: 1, b: 2, c: 3 }

イミュータブル操作(元の配列を変更しないパターン)

Reactや状態管理(Redux等)では元の配列を変更しないイミュータブルな操作が必須だ。破壊的なメソッドと非破壊的なメソッドを整理する。

// ─── 破壊的メソッド(元の配列を変更する)───
const arr = [1, 2, 3, 4, 5];
arr.push(6);       // 末尾追加 → arr=[1,2,3,4,5,6]
arr.pop();         // 末尾削除 → arr=[1,2,3,4,5]
arr.shift();       // 先頭削除 → arr=[2,3,4,5]
arr.unshift(1);    // 先頭追加 → arr=[1,2,3,4,5]
arr.splice(2, 1);  // インデックス2から1件削除 → arr=[1,2,4,5]
arr.sort();        // ソート(元を変更)
arr.reverse();     // 逆順(元を変更)

// ─── 非破壊的な代替(Reactなどで推奨)───
const original = [1, 2, 3, 4, 5];

// 末尾追加: スプレッド
const added = [...original, 6]; // [1,2,3,4,5,6]

// 先頭追加
const prepended = [0, ...original]; // [0,1,2,3,4,5]

// 特定インデックスを削除
const removed = original.filter((_, i) => i !== 2); // インデックス2を除く
// [1,2,4,5]

// 特定インデックスを更新
const updated = original.map((v, i) => i === 2 ? 99 : v);
// [1,2,99,4,5]

// 特定インデックスに挿入
const inserted = [
  ...original.slice(0, 2),
  99,
  ...original.slice(2),
];
// [1,2,99,3,4,5]

// ソート(元を変更しない)
const sorted = [...original].sort((a, b) => b - a); // 降順
// [5,4,3,2,1](originalは変わらない)

よくある質問

Q. forとforEachはどちらを使うべきですか?

一般的なWebアプリ開発では可読性が高いforEachやfor…ofを推奨する。速度差は100万件で数ミリ秒程度であり、実用上の問題にはほとんどならない。ゲームエンジンや音声処理など、毎フレーム数万回以上ループが走る場面ではforやwhileを検討しよう。

Q. mapとforEachはどちらを使えばいいですか?

判断は簡単だ。変換した新しい配列が必要 → map、副作用だけ起こしたい → forEach。この二択で迷うことはなくなる。

Q. reduceが難しくて使いこなせません。

最初は「合計を求める」だけから始めよう。reduce((acc, val) => acc + val, 0)を手で書けるようになったら、次に「グループ化」に挑戦する。無理にreduceで全てを書く必要はなく、forループやmapで読みやすく書けるなら、そちらを選ぶのが正解だ。

Q. for…inで配列をループするのはなぜいけないの?

for...inはオブジェクトのキー列挙用で、プロトタイプチェーン上のプロパティも拾う可能性がある。配列にはfor...offorEachを使おう。

Q. パフォーマンスの差はブラウザとNode.jsで違いますか?

はい。ChromeのV8エンジンはNode.jsと同じエンジンなので傾向は近いが、Firefox(SpiderMonkey)やSafari(JavaScriptCore)では挙動が異なる。クリティカルな処理は実際に動かす環境でベンチマークすることを強く勧める。

Q. some・every・flatMap はどういう時に使いますか?

// some: 条件を満たす要素が「1つでも」あるか
const hasAdult = [15, 18, 20].some(age => age >= 18); // true

// every: 「全て」の要素が条件を満たすか
const allAdult = [18, 20, 25].every(age => age >= 18); // true

// flatMap: map + flat を一度に
const sentences = ['Hello World', 'foo bar'];
const words = sentences.flatMap(s => s.split(' '));
console.log(words); // ['Hello', 'World', 'foo', 'bar']

まとめ

配列の繰り返しは「目的」で手法を選べ
変換 → map、副作用 → forEach、集約 → reduce、条件絞り → filter
速度差は100万件で数msレベル。普段は可読性優先でOK

JavaScriptの配列処理を正しく使い分けられると、コードの意図が明確になり、チーム開発でもバグが減る。まずは今日のコードで「mapを副作用に使っていないか」だけチェックしてみよう。

指標実績調査対象・期間
案件獲得率97.9%受講生アンケート/2025年1月〜12月
受講料金完済率84.7%受講生アンケート/2025年6月〜12月
運営体制Web制作会社が直接運営現役制作会社が直接指導

※受講生アンケートに基づく自社調査結果(2025年1月〜2025年12月実施)
※受講生アンケートに基づく自社調査結果(2025年6月〜2025年12月実施)


WithCodeを体験できる初級コース公開中!

初級コース(¥49,800)が完全無料に!

  • 期間:1週間
  • 学習内容:
    ロードマップ/基礎知識/環境構築/HTML/CSS/LP・ポートフォリオ作成
    正しい学習方法で「確かな成長」を実感できるカリキュラム。

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?

未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!

この記事を書いた人

WithCodeでWeb制作を習得後、フリーランスエンジニアとして活動。HTML/CSS・JavaScript・WordPress案件を中心に年間20件以上の制作実績を持つ。「難しい技術をわかりやすく」をモットーに、初心者〜中級者向けの技術記事を執筆。副業・フリーランス独立を目指す方に向けた情報発信に注力している。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次