何が違うのか
map は「各要素を同じルールで変換して“新しい配列”を返す」メソッドです。一方、forEach は「各要素に対して“処理(副作用)を実行するだけ”で、配列は返しません」。ここが重要です:結果の配列が欲しいなら map、ログ出力や外部書き込みなど“実行すること”が目的なら forEach。役割を混ぜるほどコードは読みにくく、バグが増えます。
基本の使い方の対比
map:変換して新しい配列を返す
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2);
console.log(doubled); // [2, 4, 6]
console.log(nums); // [1, 2, 3](元は不変)
JavaScriptmap のコールバックは「入力→出力」を返します。返した値が新しい配列に入るため、処理の意図が明確でテストしやすいです。オブジェクト要素を安全に変換する場合は、スプレッドで新しいオブジェクトを返します。
const users = [{ id: 1, name: "A" }, { id: 2, name: "B" }];
const withFlag = users.map(u => ({ ...u, active: true }));
JavaScriptforEach:副作用を実行するだけ
const nums = [1, 2, 3];
let sum = 0;
nums.forEach(n => { sum += n; }); // 集計という“副作用”
console.log(sum); // 6
JavaScriptforEach は戻り値を返しません(undefined)。新しい配列が欲しい場面で forEach を使うと、外部配列に push する“副作用の塊”になり、意図が不透明になります。変換・抽出は map/filter に任せ、forEach は“実行”に徹するのが安全です。
制御フローの違い
早期終了・スキップの扱い
forEach の中では break は使えず、return は「その要素だけ終了」します。ループ自体は止まりません。途中で完全停止したいなら for…of/for、存在判定なら some、最初の一致取得なら find を使います。
// forEach では止められない
[1, 2, 3].forEach(n => {
if (n === 2) return; // 2の処理をスキップ、ループは続く
console.log(n);
});
// 途中停止したいなら for...of
for (const n of [1, 2, 3]) {
if (n === 2) break;
console.log(n); // 1
}
JavaScript非同期処理の違い(ここが重要)
forEach の中で async/await を使っても“外側は待ちません”。順序どおりに待ちながら処理したいなら for…of+await、並列で投げてまとめたいなら map で Promise の配列を作り、Promise.all で待機します。
// 並列に投げて待つ
const ids = [1, 2, 3];
const promises = ids.map(id => fetch(`/api/${id}`).then(r => r.json()));
const results = await Promise.all(promises);
// 逐次で確実に待つ
const out = [];
for (const id of ids) {
const res = await fetch(`/api/${id}`);
out.push(await res.json());
}
JavaScript疎配列・undefined の扱い
どちらも「空スロット(疎配列の穴)」をスキップします。値としての undefined はコールバックに渡されますが、穴はそもそも呼ばれません。穴も含めて処理したい場合は、先に正規化(Array.from や fill)します。
const sparse = Array(3); // [ <3 empty items> ]
// map/forEach は穴を呼ばない
console.log(sparse.map(x => 0)); // [ <3 empty items> ]
sparse.forEach((v, i) => console.log(i, v)); // 何も出ない
// 正規化してから処理
const normalized = Array.from(sparse, x => x ?? null);
console.log(normalized.map(x => 0)); // [0, 0, 0]
normalized.forEach((v, i) => console.log(i, v)); // 0 null, 1 null, 2 null
JavaScriptオブジェクトを扱う場合の書き方
Object.entries+map:オブジェクトから配列へ変換
オブジェクトはイテラブルではないため、keys/values/entries を使います。表示用の行へ整形するなど“変換”は map が適任です。
const user = { id: 1, name: "Alice", email: "a@x" };
const pairs = Object.entries(user).map(([k, v]) => `${k}=${v}`);
console.log(pairs); // ["id=1","name=Alice","email=a@x"]
JavaScriptObject.entries+forEach:辞書やDOMへ副作用
副作用(辞書投入、DOM 操作、ログ)は forEach が自然です。
const dict = new Map();
Object.entries(user).forEach(([k, v]) => { dict.set(k, v); });
JavaScript使い分けの指針(重要ポイントの深掘り)
「新しい配列が欲しいか?」が分水嶺です。欲しいなら map、要らないなら forEach。map のコールバックは“純粋(副作用なし)”に保つと、テスト容易性・再利用性が上がります。forEach は副作用に集中させ、停止が必要なら他の構文/メソッドへ切り替えます。非同期は map+Promise.all(並列)か for…of+await(逐次)に設計を分けるのが鉄則です。疎配列の穴はスキップされるため、欠損を扱う要件なら前処理で正規化してからループに入ります。オブジェクトの変換は Object.entries+map、投入は forEach と役割を明確に分けると読みやすさが一気に上がります。
よくある落とし穴と回避策
map を使っているのにコールバックで外部配列へ push するなど“副作用”を混ぜると、意図が曖昧になりバグの温床です。map は返り値だけで形を作り、push は使わない。forEach の中で break/await しようとするのも典型的な誤りです。止めたい/待ちたい要件なら素直に for…of や Promise.all に切り替えます。さらに、配列をループ中に破壊的変更(splice/push)すると境界が動いて取りこぼしが発生します。結果は別配列に入れる非破壊パターンにしましょう。
まとめ
map は「変換して新しい配列を返す」、forEach は「副作用を実行するだけ」。停止・スキップ・非同期の扱い、疎配列の挙動、オブジェクトの扱い(entries 経由)まで理解すれば、どちらを選ぶべきか迷いません。結果が欲しいなら map、実行が目的なら forEach。停止は for…of、並列は Promise.all。役割を明確に分け、コールバックは“純粋”に保つことで、初心者でも短く明確で壊れにくいループ処理を書けます。
