読み込み中...

JavaScriptエンジニア必見!forEach・mapの目からウロコな使い分け術10選

JavaScriptのforEachとmapのサンプルコード JS
この記事は約22分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

JavaScriptで配列を扱うなら、forEachは副作用を伴う反復処理、mapは値を変換して新しい配列を作る処理に使います。戻り値を使わないならforEach()、戻り値を渡すならmap()です。

その違いを押さえると、console.log()の確認、DOM反映、APIレスポンス整形を分けやすくなります。初心者がつまずきやすいのは、forEachの戻り値を配列として使う点と、mapを単なるループにする点でしょう。

MDNのArray.prototype.forEach()は各要素に関数を一度ずつ実行し、戻り値はundefinedです。一方、Array.prototype.map()は各要素に関数を適用し、その結果から新しい配列を作ります。

動作確認環境
  • JavaScript: ECMAScript 2024 相当の構文
  • ブラウザ: Google Chrome 126 以降、Firefox 127 以降、Safari 17 以降
  • Node.js: 20 LTS 以降
📖 この記事で学べること
  • forEachmapの戻り値と副作用の違い
  • callbackFnindexarrayの使い方
  • NodeListDOMaddEventListenerでの反復処理
  • Promise.allmapを組み合わせた非同期処理
  • filterreducefindとの実用的な使い分け

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

forEachとmapの基本を押さえよう!

結論は、処理の目的が「実行」ならforEach、「変換」ならmapです。最小コードで比べると、forEach()は画面やログなど外側へ影響を出し、map()は戻り値として新しいArrayを返す点がはっきり分かります。

const fruits = ['りんご', 'バナナ', 'オレンジ'];

fruits.forEach((fruit) => {
  console.log(fruit);
});

const labels = fruits.map((fruit) => `${fruit}があります`);
console.log(labels);

結果: 期待される出力は、forEach側で果物名が順番に表示され、map側で文字列に変換された配列が表示される形です。

この違いは、コードを読む人にも処理意図を伝えます。forEachが出てきたら「各要素に対して何かを実行する」と読み、mapが出てきたら「各要素から別の値を作る」と理解すると整理しやすいでしょう。

そのため、JavaScriptのforEachでreturnを活用する6つのテクニックのようにreturnを扱う場合でも、forEachの返値は使えません。returnはコールバックから抜けるだけで、反復全体を止める仕組みではありません。

forEachの基本構文

forEach()callbackFnelementindexarrayを渡します。第1引数だけでも使えますが、要素番号や配列全体を参照したい場面では第2引数と第3引数が役立つ。

const scores = [82, 91, 76];

scores.forEach((score, index, array) => {
  const last = index === array.length - 1;
  console.log(`${index + 1}件目: ${score}${last ? ' end' : ''}`);
});

結果: 期待される出力は、各点数に件数が付き、最後の要素だけendが付く形です。

このときarray.lengthを参照できるため、前後の要素と比較する処理にも使えます。ただし、forEachは途中で自然にbreakできないため、条件に合う要素を探すだけならfind()some()のほうが扱いやすい。

mapの基本構文

map()は各要素から戻した値を集めて、新しい配列を返します。元の配列を直接変えずにidnamecreatedAtなどの形式を整えられるため、表示用データの作成と相性がよい。

const users = [
  { id: 1, name: '田中' },
  { id: 2, name: '鈴木' },
  { id: 3, name: '佐藤' }
];

const names = users.map((user) => user.name);
console.log(names);
console.log(users);

結果: 期待される出力は、namesに名前だけの配列、usersに元のオブジェクト配列が表示される形です。

一方、mapのコールバック内でconsole.log()だけを実行し、戻り値を使わない書き方は意図が読み取りにくくなります。その処理は副作用が目的なので、forEachへ寄せるほうが自然です。

観点forEachmap判断基準関連する構文
戻り値undefined新しい配列戻り値を使うならmapreturn
主な目的副作用の実行値の変換ログやDOMはforEachconsole.log
元配列書き方次第で変更可能直接変更しない設計にしやすい不変性を保つならmap...
途中終了通常は不可変換用途なので不可探索ならfindsome
非同期awaitを待つ用途に不向きPromise配列を作りやすい並列ならPromise.allasync
DOM操作複数要素への反映に向く表示データの生成に向く画面変更はforEachquerySelectorAll
チェーンつなげにくいつなげやすい加工の連鎖はmapfilter
型変換副作用として代入しがち変換結果を集めやすい文字列数値変換はNumberNumber()

forEachとmapはどう使い分ける?

使い分けの軸は、処理後に配列が必要かどうかです。forEachclassList.add()addEventListener()、ログ出力のように外側へ作用する処理で読みやすく、mapはAPIデータを画面用の形へ変える処理で意図が伝わります。

具体的には、JavaScriptイベント徹底解説!30個の使い方と応用例で扱うイベント登録はforEachと相性があります。反対に、一覧ラベルや集計用の数値はmapで新しい配列にすると後続処理へ渡しやすい。

const numbers = [1, 2, 3];

numbers.forEach((num) => {
  console.log(`数字: ${num}`);
});

const numberLabels = numbers.map((num) => `数字: ${num}`);
console.log(numberLabels);

結果: 期待される出力は、forEachで個別の行が表示され、mapで文字列配列が表示される形です。

そのため、画面やログに出すだけならforEach、作った文字列を保存・結合・描画へ回すならmapと考えます。処理目的を変数名へ反映すると、numberLabelsの意味も明確になる。

💡 Tips: forEachmapの迷いは「戻り値を使うか」でほぼ整理できます。新しい配列が不要ならforEach、必要ならmapという基準に寄せると、読み手が処理の意図を追いやすくなるのが基本です。

一方、実装パターンとしてよく見るのは、mapで必要な項目だけに変換してから、forEachで表示や送信を行う流れです。変換と副作用を分けると、片方の変更がもう片方へ影響しにくくなるのが基本です。

const users = [
  { id: 1, name: '田中', age: 25 },
  { id: 2, name: '鈴木', age: 30 },
  { id: 3, name: '佐藤', age: 28 }
];

const userCards = users.map((user) => ({
  title: `${user.name}さん`,
  description: `${user.age}歳`
}));

userCards.forEach((card) => {
  console.log(`${card.title}: ${card.description}`);
});

結果: 期待される出力は、名前と年齢を整形した文字列がユーザーごとに表示される形です。

この分離はテストにも効きます。userCardsは入力と出力の比較がしやすく、表示処理は副作用だけに絞れます。

実践で使える!forEach活用テクニック5選

forEachは配列を回しながら外側へ影響を出す場面で使いるのが目安です。特に押さえたいのは、インデックス付き処理、DOM操作、オブジェクト更新、非同期処理、大量データへの配慮です。

配列の各要素を簡単操作

これらの処理で最も素直なのは、要素とindexを一緒に扱う書き方です。番号付き表示、入力順の保持、隣接要素との比較など、単純なfor文で書いていた処理を読みやすく置き換えられます。

const items = ['りんご', 'バナナ', 'オレンジ'];
const cart = [];

items.forEach((item, index) => {
  cart.push({
    id: `item-${index + 1}`,
    name: item,
    label: `${index + 1}番目の商品`
  });
});

console.log(cart);

結果: 期待される出力は、idnamelabelを持つオブジェクト配列です。

ただし、この例はcart.push()という副作用を使っています。新しい配列を作るだけならmapのほうが短く、変数の再代入や外部配列の変更も避けやすいでしょう。

DOM操作をスッキリ書く

document.querySelectorAll()が返すNodeListには、多くの現代的なブラウザでforEachを使えます。ボタンにクラスを付ける、イベントを登録する、入力欄をまとめて検査する処理では、戻り値より副作用が主役になる。

const buttons = document.querySelectorAll('.js-action');

buttons.forEach((button) => {
  button.classList.add('is-ready');
  button.addEventListener('click', () => {
    button.classList.toggle('is-active');
  });
});

結果: 期待される表示は、対象ボタンへis-readyクラスが付き、クリック時にis-activeが切り替わる状態です。

この処理は、JavaScriptにおけるイベントハンドラを完全ガイド!20選の実践サンプルコード付きで扱うイベント登録の基礎にもつながります。ただし、古い環境ではArray.from()で配列化する選択肢もあります。

オブジェクト配列の処理

オブジェクト配列をforEachで処理すると、既存オブジェクトへ計算結果を追加できるのがポイントです。もっとも、元データを変える設計になるため、変更を許容するデータなのかを先に決める必要があるのが目安です。

const users = [
  { id: 1, name: '田中', points: [80, 90, 75] },
  { id: 2, name: '鈴木', points: [85, 95, 80] },
  { id: 3, name: '佐藤', points: [90, 85, 85] }
];

users.forEach((user) => {
  const total = user.points.reduce((sum, point) => sum + point, 0);
  user.average = Math.round((total / user.points.length) * 10) / 10;
});

console.log(users);

結果: 期待される出力は、各ユーザーにaverageプロパティが追加された配列です。

逆に、元配列を保ったまま平均点付きの配列を作りたい場合はmapを使います。...のスプレッド構文で既存プロパティを展開し、新しいaverageだけを追加すると意図が明確になる。

非同期処理との組み合わせ

forEachのコールバックにasyncを付けても、外側のforEachawaitを待ちません。MDNのforEach()解説でも、プロミスを待たない点が注意点として示されています。

async function fetchUsers(users) {
  const requests = users.map((user) => fetch(`/api/users/${user.id}`));
  const responses = await Promise.all(requests);

  responses.forEach((response) => {
    console.log(response.status);
  });
}

結果: 期待される出力は、すべてのfetchが完了したあとに各レスポンスのstatusが表示される形です。

そのため、並列に取得したい場合はmapPromise配列を作り、Promise.all()へ渡します。順番に待ちたい場合はfor...ofawaitを組み合わせるほうが読みやすい。

⚠️ 注意: forEach内のasync関数は、反復全体の完了を表すPromiseを返しません。完了待ちが必要な処理ではPromise.allまたはfor...ofを検討します。

パフォーマンスを意識した使い方

大量データを扱うときは、forEachそのものより、ループ内で何を作るかが効きますし、ここがポイントです。毎回新しい関数や大きなオブジェクトを作ると、メモリ確保と破棄が増える可能性があるのがポイントです。

一般に、DOM更新はまとめて行い、計算済みの値は変数へ保持します。ただし、負荷はデータ量や処理内容で変わるため、読みやすさと不要な生成の削減を基準にするとよい。

現場で役立つ!mapメソッド実践テクニック5選

mapは、元データを表示用・送信用・集計用の別形式へ変えるときに使いるのが一般的です。APIレスポンスのキー変換、ネストした配列の整形、条件付きの値追加、メソッドチェーンなど、戻り値を中心に設計できる点が特徴です。

データ変換の基本パターン

APIのレスポンスは、サーバー側の命名規則に合わせてsnake_caseになっていることがあります。フロントエンド側でcamelCaseへ寄せるなら、mapで表示に近い形へ変換すると後続処理が読みやすくなります。

const apiResponse = [
  { user_id: 1, user_name: 'tanaka', created_at: '2024-01-01' },
  { user_id: 2, user_name: 'suzuki', created_at: '2024-01-02' }
];

const users = apiResponse.map((user) => ({
  id: user.user_id,
  name: user.user_name,
  createdAt: new Date(user.created_at)
}));

console.log(users);

結果: 期待される出力は、user_ididuser_namenamecreated_atDateに変換された配列です。

この変換を早い段階で済ませると、テンプレート側でuser.user_iduser.idが混在しにくくなります。日付表示はtoLocaleDateString()で表示専用の値を作れます。

オブジェクト配列の変換術

注文データのようにネストした配列を含む場合も、外側のmapと内側のreduceを組み合わせると集計値を作れますが、これは押さえたい点です。quantitypriceから小計を出し、注文単位の合計へ変換する流れです。

const orders = [
  {
    id: 1,
    items: [
      { name: '商品A', price: 1000, quantity: 2 },
      { name: '商品B', price: 2000, quantity: 1 }
    ]
  }
];

const summaries = orders.map((order) => ({
  orderId: order.id,
  totalAmount: order.items.reduce((sum, item) => {
    return sum + item.price * item.quantity;
  }, 0),
  itemCount: order.items.length
}));

console.log(summaries);

結果: 期待される出力は、注文ID、合計金額、商品数を持つ集計用オブジェクトの配列です。

ただし、itemsが存在しない可能性があるデータでは?.のオプショナルチェーンや??のNull合体演算子を使います。欠損データを空配列として扱う設計にすれば、画面側の条件分岐を減らせるでしょう。

複雑なデータ構造の整形

ツリー構造をセレクトボックスやメニュー用の形式に変える場合、親と子を同じルールで整形すると扱いやすくなります。childrenがないノードもあり得るため、存在確認を変換式に含めるのが現実的です。

const departments = [
  {
    id: 1,
    name: '開発部',
    children: [
      { id: 2, name: 'フロントエンド' },
      { id: 3, name: 'バックエンド' }
    ]
  }
];

const options = departments.map((dept) => ({
  label: dept.name,
  value: dept.id,
  children: dept.children?.map((child) => ({
    label: child.name,
    value: child.id
  })) ?? []
}));

console.log(options);

結果: 期待される出力は、親部署と子部署がlabelvalue形式にそろった配列です。

同様に、ファイル名や拡張子の整形でもmapは使えます。JavaScriptで拡張子を活用する方法12選!初心者でも簡単にできる方法を紹介のような文字列処理では、split()slice()で分解した値を配列へ戻せます。

条件付きマッピング

条件によって値を変えたいときも、mapの戻り値をオブジェクトにすると情報をまとめられますし、これが一つの目安です。roleから権限を作る、scoreから判定を作る、といった変換では三項演算子やifを使い分けますし、ここがポイントです。

const members = [
  { id: 1, name: '田中', role: 'admin' },
  { id: 2, name: '鈴木', role: 'user' }
];

const viewModels = members.map((member) => ({
  ...member,
  canEdit: member.role === 'admin',
  displayName: member.role === 'admin'
    ? `${member.name}(管理者)`
    : member.name
}));

console.log(viewModels);

結果: 期待される出力は、各メンバーにcanEditdisplayNameが追加された配列です。

このとき、条件が増えすぎるとmapの中身が読みにくくなります。判定ロジックをgetPermission()formatDisplayName()のような関数へ分けると、変換の流れを保ったまま個別条件を管理できます。

チェーンでの活用法

mapfilterreduceと組み合わせると、抽出、変換、集計を一続きで書けるのが現実的です。ただし、チェーンが長いと読み手が中間状態を追いにくくなるため、意味のある単位で変数に分ける判断も必要です。

const transactions = [
  { id: 1, type: 'sale', amount: 1000 },
  { id: 2, type: 'refund', amount: -500 },
  { id: 3, type: 'sale', amount: 2000 }
];

const saleAmounts = transactions
  .filter((transaction) => transaction.type === 'sale')
  .map((transaction) => transaction.amount);

const saleTotal = saleAmounts.reduce((sum, amount) => sum + amount, 0);
console.log(saleTotal);

結果: 期待される出力は、販売データだけを合計した3000です。

一方、日付配列を画面用データへ変換する処理では、JavaScriptでカレンダーを作成!サンプルコード10選で簡単理解のように、日付、曜日、表示ラベルをmapで整形できます。表示側は整形済みデータを受け取るだけです。

ℹ️ 補足: mapfilterreduceを連結すると読みやすい場面もありますが、大量データでは中間配列が増えます。読みやすさを優先しつつ、負荷が気になる処理だけreduceなどで集約するとよいでしょう。

こんなときどうする?トラブルシューティング

初心者がつまずきやすいのは、戻り値、途中終了、型変換、非同期です。これらはforEachmapの仕様から起こるため、書く前にメソッドの役割を合わせるほうが安定すると整理できるのが一般的です。

forEachで値を返そうとしてしまう

forEachの戻り値は常にundefinedです。配列から条件に合う値を取り出したいなら、findfiltersomeのように目的に合うメソッドを使います。

const users = [
  { id: 1, name: '田中' },
  { id: 5, name: '佐藤' }
];

const wrong = users.forEach((user) => {
  if (user.id === 5) return user;
});

const found = users.find((user) => user.id === 5);
console.log(wrong);
console.log(found);

結果: 期待される出力は、wrongundefinedfoundid 5のユーザーオブジェクトです。

この例では、return userforEach全体の戻り値にならない点が原因です。条件に合う1件だけが必要ならfind、該当する複数件が必要ならfilterを選ぶと目的が合います。

mapの結果を使っていない

mapは新しい配列を作るメソッドなので、戻り値を捨てると処理意図が薄くなります。ログ出力や外部変数への追加だけなら、forEachを使うほうが読み手に伝わりやすい。

const data = ['a', 'b', 'c'];

data.forEach((item) => {
  console.log(item.toUpperCase());
});

const upper = data.map((item) => item.toUpperCase());
console.log(upper);

結果: 期待される出力は、forEach側で大文字文字列が個別に表示され、map側で大文字の配列が表示される形です。

そのため、コードレビューでは「戻り値を使っているか」を確認すると判断しやすくなります。戻り値が不要ならforEach、必要ならmapという単純な線引きが有効です。

文字列と数値の変換ミス

フォームやCSVから受け取った値は文字列になりやすく、+が数値加算ではなく文字列結合として働く場合があります。mapで数値配列へ変換するなら、Number()parseInt()を明示すると理解できるのが現実的です。

const values = ['1', '2', '3'];

const wrong = values.map((value) => value + 1);
const fixed = values.map((value) => Number(value) + 1);

console.log(wrong);
console.log(fixed);

結果: 期待される出力は、wrong['11', '21', '31']fixed[2, 3, 4]です。

この問題はtypeofで中間値を確認すると原因を追いやすくなります。小数を含む値ならNumber()、整数だけを扱う入力なら基数を意識してparseInt(value, 10)を使う判断もあります。

デバッグと負荷の見直し

デバッグでは、変換前、変換中、変換後のどこで値が崩れたかを切り分けますが、覚えておくと役立つでしょう。console.table()console.group()debuggerを使うと処理順を確認しやすい。

ただし、ログを残したまま大量データを処理すると、ブラウザのコンソール表示が重くなることがあると整理できます。開発時だけdebugModeを有効にし、本番用コードでは不要なログを取り除く運用が一般的です。

⚠️ 注意: 非推奨APIを代替なく使う説明は避けるべきです。配列処理ではdocument.writeのような古い出力方法に寄せず、textContentappend、テンプレート描画など、用途に合う現行の書き方を選びます。

まとめ

forEachmapの違いは、戻り値と副作用で判断できると覚えるとよいでしょう。forEachは各要素に処理を実行してundefinedを返し、mapは各要素の変換結果から新しい配列を返します。

そのため、DOMへの反映、イベント登録、ログ出力、既存データの更新はforEachが合いやすく、APIレスポンスの整形、表示用データ作成、条件付き変換、集計前の抽出はmapが扱いやすいと言えますが、これは押さえたい点です。途中終了ならfindsome、集計ならreduceへ切り替えると目的に合いると考えられます。

これらを使い分けると、JavaScriptの配列操作は「反復」「変換」「探索」「集計」に分けて読めます。forEachmapは、処理後に何が残るのかを基準に選ぶのが実用的。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。