読み込み中...

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

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

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

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

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

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

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

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

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

配列操作に困っていませんか?JavaScriptで配列を扱う際、避けては通れないforEachとmapメソッド。

「どう使い分ければいいの?」「どっちを使うのが正解?」という声をよく耳にします。

この記事では、現場で本当に使える実践的なforEachとmapの使い方を、初心者にもわかりやすく解説します。

基礎から応用まで、具体的なコード例を交えながら徹底的に理解していきましょう!

そもそもforEachとは?

forEachは、配列の各要素に対して同じ処理を行うためのメソッドです。

従来のfor文よりもシンプルに書け、配列の長さを気にする必要が少なくなるので、結果的に大体コードがスッキリします!

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

fruits.forEach((fruit) => {
    console.log(fruit);
});
// 出力...
// りんご
// バナナ
// オレンジ

🔍 ここがポイント!
forEachは「配列の要素を1つずつ取り出して、それぞれに同じ処理をする」という単純な繰り返し処理に最適です。

○mapメソッドの基本的な使い方

mapの特徴は「新しい配列を返す」ことです。

元の配列はそのままで、処理結果を別の配列として取得できます。

const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map((num) => {
    return num * 2;
});

console.log(doubled); // [2, 4, 6, 8, 10]
console.log(numbers); // [1, 2, 3, 4, 5] (元の配列は変更されない)

オブジェクトの配列から特定のプロパティだけを抽出する場合もmapが便利です。

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

const names = users.map(user => user.name);
console.log(names); // ['田中', '鈴木', '佐藤']

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

forEachとmapの使い分けで迷ったときは、以下のポイントを見て確認してください。

🔍 使い分けのポイント!

forEachを使うべき場合・・・

  • 単純に配列の要素を順番に処理したいとき
  • 配列の要素を使って副作用(画面表示やデータベース更新など)を発生させたいとき
  • 新しい配列を作る必要がないとき

mapを使うべき場合・・・

  • 配列の要素を変換して新しい配列を作りたいとき
  • データの加工や変換が必要なとき
  • 元の配列を保持しておきたいとき
// <<forEachの例>>単純な表示
const numbers = [1, 2, 3];
numbers.forEach(num => console.log(`数字: ${num}`));

// mapの例:データ変換
const numberStrings = numbers.map(num => `数字: ${num}`);

実際の現場では、以下のようにmapとforEachを組み合わせて使うことも多々あります。

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

// まずmapで必要なデータを抽出
const userInfo = users.map(user => ({
    name: user.name,
    birthYear: 2024 - user.age
}));

// その後forEachで表示
userInfo.forEach(info => {
    console.log(`${info.name}さんは${info.birthYear}年生まれです`);
});

⚠️ よくある間違い

  • forEachの戻り値を変数に代入しても、必ずundefinedが返ります
  • mapで新しい配列を作らないのは非効率的です

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

forEachの基本は理解できましたね。

ここからは、実践的な場面で使える具体的なテクニックを見ていきましょう!

現場でよく遭遇するシーンに沿って解説します。

○配列の各要素を簡単操作

forEachの真骨頂は、シンプルな繰り返し処理をクリーンに書けること。

インデックスも活用できます!

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

// インデックスと要素の両方を使う
items.forEach((item, index) => {
    console.log(`${index + 1}番目: ${item}`);
});

// 配列の要素を加工する
const cart = [];
items.forEach(item => {
    cart.push({
        name: item,
        id: Math.random().toString(36).slice(-8)
    });
});

💡Tips

第3引数には配列自体が渡せます。これを使えば、以下のように元の配列を参照しながら処理できます。

const numbers = [1, 2, 3, 4, 5];
numbers.forEach((num, index, array) => {
    if (index < array.length - 1) {
        console.log(`現在: ${num}, 次: ${array[index + 1]}`);
    }
});

○DOM操作をスッキリ書く

forEachを使えば、複数の要素に対する DOM操作(Webページの要素を操作すること)がとても簡潔に書けます。

// クラスの一括追加
const buttons = document.querySelectorAll('.btn');
buttons.forEach(button => {
    button.classList.add('hover-effect');
    button.addEventListener('click', () => {
        button.classList.add('clicked');
    });
});

// フォームの入力値を一括チェック
const inputs = document.querySelectorAll('input[required]');
let isValid = true;
inputs.forEach(input => {
    if (!input.value) {
        input.classList.add('error');
        isValid = false;
    }
});

NodeListは配列ではありませんが、forEachが使えます。ただし、IE11では注意が必要です!

○オブジェクト配列の処理

複雑なデータ構造も、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 average = user.points.reduce((sum, point) => sum + point, 0) / user.points.length;
    user.average = Math.round(average * 10) / 10;
});

🔍 ここがポイント!
オブジェクトの中のプロパティも自由に更新できます。

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

⚠️ 注意点
forEachは非同期処理を直列実行(順番に実行すること)するのには向いていません。

// ❌ 良くない例
async function fetchUserData(users) {
    users.forEach(async user => {
        const data = await fetch(`/api/users/${user.id}`);
        console.log(data);
    });
}

// ✅ 良い例:Promse.allを使う
async function fetchUserData(users) {
    const promises = users.map(user => fetch(`/api/users/${user.id}`));
    const results = await Promise.all(promises);
    results.forEach(data => console.log(data));
}

非同期処理を並列で行いたい場合は、mapでPromiseの配列を作ってからPromise.allを使いましょう。

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

🔍 最適化のポイント!

  • 大きな配列の場合は、処理を分割することを検討
  • 不要な関数生成を避ける
  • 条件分岐は最小限に
const bigArray = new Array(10000).fill(0);

// ❌ 良くない例:ループ内で関数を生成
bigArray.forEach(item => {
    const processor = () => {
        // 重い処理
    };
    processor();
});

// ✅ 良い例:関数を外で定義
const processor = () => {
    // 重い処理
};
bigArray.forEach(processor);

💡Tips

  • 配列が1000要素を超える場合は、バッチ処理を検討
  • DOMの更新は可能な限りまとめる
  • 計算結果のキャッシュを活用する

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

実務でmapメソッドを使いこなすことで、データ加工の作業が格段に効率化します。

ここでは、現場で本当に使える実践的なテクニックを一緒に覚えていきましょう。

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

mapの基本は「入力→加工→出力」のシンプルな変換です。

この基本パターンをマスターすることで、複雑な変換も簡単に行いましょう。

// ✅ APIレスポンスの整形(実務でよくあるパターン)
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)
}));

// 🚀 日付のフォーマット変換も簡単
const formattedUsers = users.map(user => ({
    ...user,
    createdAt: user.createdAt.toLocaleDateString('ja-JP')
}));

スプレッド構文(...)を使うと、一部のプロパティだけを変更する変換が簡単です!

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

複雑なオブジェクト配列も、mapを使えば上品に変換できます。

// 🎯 ネストされたデータの変換
const orders = [
    {
        id: 1,
        items: [
            { name: "商品A", price: 1000, quantity: 2 },
            { name: "商品B", price: 2000, quantity: 1 }
        ]
    }
];

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

// 📊 集計データの作成
const itemDetails = orders.map(order => {
    const total = order.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
    return {
        id: order.id,
        items: order.items.map(item => ({
            ...item,
            subtotal: item.price * item.quantity,
            percentage: ((item.price * item.quantity) / total * 100).toFixed(1)
        }))
    };
});

⚠️ 注意点
ネストが深いオブジェクトを変換する際は、nullチェックを忘れずに!

○複雑なデータ構造の整形

複雑なデータ構造も、mapを使えば見通しよく整形できます。

// 🌲 ツリー構造の変換
const departments = [
    {
        id: 1,
        name: "開発部",
        children: [
            { id: 2, name: "フロントエンド" },
            { id: 3, name: "バックエンド" }
        ]
    }
];

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

// 📊 集計付きの変換
const salesData = [
    { month: '2024-01', sales: [100, 200, 300] }
];

const analyzedData = salesData.map(data => ({
    month: data.month,
    sales: data.sales,
    total: data.sales.reduce((sum, sale) => sum + sale, 0),
    average: Math.round(data.sales.reduce((sum, sale) => sum + sale, 0) / data.sales.length)
}));

○条件付きマッピング

条件分岐を含むマッピングも、三項演算子や論理演算子を使えばスッキリ書けます。

// 🎯 条件に応じた変換
const users = [
    { id: 1, name: "田中", role: "admin" },
    { id: 2, name: "鈴木", role: "user" }
];

const userPermissions = users.map(user => ({
    ...user,
    canEdit: user.role === "admin",
    displayName: user.role === "admin" ? `${user.name}(管理者)` : user.name
}));

// 🚦 複数条件の分岐
const scores = [85, 60, 95, 40];
const grades = scores.map(score => {
    if (score >= 90) return { score, grade: 'A', pass: true };
    if (score >= 80) return { score, grade: 'B', pass: true };
    if (score >= 70) return { score, grade: 'C', pass: true };
    return { score, grade: 'F', pass: false };
});

○チェーンでの活用法

mapは他のメソッドと組み合わせることで、より柔軟な変換が可能になります。

// 🔗 filterとの組み合わせ
const transactions = [
    { id: 1, type: "sale", amount: 1000 },
    { id: 2, type: "refund", amount: -500 },
    { id: 3, type: "sale", amount: 2000 }
];

const saleTotal = transactions
    .filter(t => t.type === "sale")
    .map(t => t.amount)
    .reduce((sum, amount) => sum + amount, 0);

// 🎯 複数のmapを使った段階的な変換
const rawData = ["1,田中,25", "2,鈴木,30"];
const processedData = rawData
    .map(row => row.split(','))
    .map(([id, name, age]) => ({
        id: Number(id),
        name,
        age: Number(age)
    }))
    .map(user => ({
        ...user,
        isAdult: user.age >= 20
    }));

💡Tips

  • チェーンが長くなる場合は、途中結果を変数に格納することを検討
  • 大量のデータを扱う場合は、必要な変換だけを行う

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

実務でforEachやmapを使っていると、思わぬ躓きポイントに遭遇することも。

ここでは、現場でよくある問題とその解決策を、具体的なコード例とともに見ていきましょう!

○よくある躓きポイントと解決策

コードを見て、まずは答えを極力見ずに問題点を考察してみましょう。

🔍 ここがポイント!
多くの問題は、メソッドの特性を理解していれば回避できます。

// ❌ <<よくあるミス1>> forEachで値を返そうとする
let found;
array.forEach(item => {
    if (item.id === 5) {
        found = item;  // breakできない!
    }
});

// ✅ 正しい解決策
const found = array.find(item => item.id === 5);

// ❌ <<よくあるミス2>> mapの結果を使わない
data.map(item => {
    console.log(item);  // 単なるループ処理なのにmapを使っている
});

// ✅ 正しい解決策
data.forEach(item => {
    console.log(item);
});

// ❌ <<よくあるミス3>> 配列の型変換ミス
const numbers = ["1", "2", "3"].map(num => num + 1);
console.log(numbers);  // ["11", "21", "31"] 文字列結合になってしまう!

// ✅ 正しい解決策
const numbers = ["1", "2", "3"].map(num => Number(num) + 1);

💡Tips

  • console.logを使って中間状態を確認
  • Chrome DevToolsのブレークポイントを活用
  • 配列の型を意識する

○デバッグのコツ

🔍 ここがポイント!
効率的なデバッグには、適切な場所での状態確認が重要です。

// 🎯 デバッグに便利なテクニック
const data = [
    { id: 1, value: "a" },
    { id: 2, value: "b" }
];

// ✅ インデックスを活用したデバッグ
data.forEach((item, index) => {
    console.log(`Index ${index}:`, item);
    // 処理の前後で値を確認
    const result = someProcess(item);
    console.log(`After process ${index}:`, result);
});

// ✅ mapでの変換過程を確認
const transformed = data.map((item, index) => {
    const intermediate = {
        ...item,
        processed: true
    };
    console.log(`Step ${index}:`, intermediate);
    return intermediate;
});

// 🔍 条件付きデバッグ
const debugMode = true;
const debug = (...args) => {
    if (debugMode) console.log(...args);
};

data.forEach((item, index) => {
    debug(`Processing item ${index}`, item);
    // 処理
});

○パフォーマンス最適化のポイント!

以下のコードを見て、パフォーマンス的によくない例と、改善した例について触れてみましょう。

// ❌ パフォーマンス的によくない例
const bigArray = new Array(10000).fill(0);

// 配列の中で新しい関数を作成(メモリ効率が悪い)
bigArray.forEach(() => {
    const processor = () => {
        // 処理
    };
    processor();
});

// ✅ パフォーマンスを改善した例
// 関数を外で定義
const processor = () => {
    // 処理
};

bigArray.forEach(processor);

// 🚀 大量データの効率的な処理
const batchProcess = (array, batchSize = 1000) => {
    let index = 0;

    const processBatch = () => {
        const batch = array.slice(index, index + batchSize);
        batch.forEach(processor);

        index += batchSize;
        if (index < array.length) {
            setTimeout(processBatch, 0);
        }
    };

    processBatch();
};

💡 最適化Tips

  1. メモリ使用量の削減
// ❌ 避けるべき
const result = bigArray.map(item => {
    return {
        ...item,
        hugeProp: new Array(1000).fill('data')
    };
});

// ✅ 必要な情報だけを保持
const result = bigArray.map(item => ({
    id: item.id,
    name: item.name
    // 必要な情報のみ
}));
  1. 処理の最適化
// ❌ 非効率な処理
const processed = data
    .map(item => item.value)
    .filter(value => value > 0)
    .map(value => value * 2);

// ✅ 1回のループで処理
const processed = data.reduce((acc, item) => {
    if (item.value > 0) {
        acc.push(item.value * 2);
    }
    return acc;
}, []);

🎯 パフォーマンスチェックポイント

  • 大きな配列の処理は分割を検討
  • 不要なオブジェクトのコピーを避ける
  • 中間配列の生成を最小限に

まとめ

ここまでforEachとmapの基本から実践的な使い方まで見てきました。

実際のプロジェクトでコードを書く際は、この記事を参考にしながら、プロジェクトの文脈に合わせて最適な実装を選択してみるのもいいと思いますよ。

是非参考に。