はじめに

JavaScriptで配列を操作するとき、forループを使って手動で処理するコードを書いていませんか。配列メソッドのmapfilterreduceを使いこなせば、より簡潔で読みやすいコードを書けるようになります。

これらのメソッドは「高階関数(Higher-Order Function)」と呼ばれ、関数を引数として受け取るという共通の特徴を持っています。本記事では、以下の内容を初心者向けにわかりやすく解説します。

  • map、filter、reduceそれぞれの基本構文と動作原理
  • 実践的なコード例と使いどころ
  • 3つのメソッドの使い分け
  • メソッドチェーンによる複合的な処理
  • パフォーマンスと可読性の注意点

高階関数とは

高階関数とは、関数を引数として受け取る関数、または関数を戻り値として返す関数のことです。mapfilterreduceはすべて、コールバック関数を引数として受け取る高階関数です。

1
2
3
4
5
6
// 高階関数の例:関数を引数として受け取る
const numbers = [1, 2, 3];

// コールバック関数を渡す
const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6]

高階関数を使うメリットは以下の通りです。

メリット 説明
宣言的なコード 「何をしたいか」を明確に表現できる
再利用性 処理のロジックを関数として分離・再利用できる
副作用の抑制 元の配列を変更せず、新しい配列を返す
チェーン可能 複数の処理を連結して記述できる

map - 各要素を変換する

mapの基本構文

mapメソッドは、配列の各要素に対してコールバック関数を実行し、その結果から新しい配列を作成します。元の配列は変更されません。

1
2
const newArray = array.map(callbackFn);
const newArray = array.map(callbackFn, thisArg);

コールバック関数は以下の引数を受け取ります。

引数 説明
element 現在処理中の要素
index 現在の要素のインデックス(省略可能)
array mapが呼び出された配列自体(省略可能)

mapの基本的な使い方

配列の各要素を変換して新しい配列を作成する場合に使用します。

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

// 各要素を2倍にする
const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

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

オブジェクト配列の変換

オブジェクトの配列から特定のプロパティだけを抽出したり、形式を変換したりする際にも便利です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const users = [
  { id: 1, name: "田中", age: 25 },
  { id: 2, name: "佐藤", age: 30 },
  { id: 3, name: "鈴木", age: 28 }
];

// 名前だけを抽出した配列を作成
const names = users.map((user) => user.name);
console.log(names); // ["田中", "佐藤", "鈴木"]

// オブジェクトの形式を変換
const formatted = users.map((user) => ({
  userId: user.id,
  displayName: `${user.name}${user.age}歳)`
}));
console.log(formatted);
// [
//   { userId: 1, displayName: "田中(25歳)" },
//   { userId: 2, displayName: "佐藤(30歳)" },
//   { userId: 3, displayName: "鈴木(28歳)" }
// ]

mapの注意点

mapは必ず元の配列と同じ長さの新しい配列を返します。要素を削除したい場合はfilterを、単純に処理を実行するだけならforEachを使用してください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const numbers = [1, 2, 3, 4, 5];

// 悪い例:条件に合わない要素でundefinedが入る
const result = numbers.map((num) => {
  if (num > 3) {
    return num * 2;
  }
  // 何も返さないとundefinedになる
});
console.log(result); // [undefined, undefined, undefined, 8, 10]

// 良い例:filterとmapを組み合わせる
const filtered = numbers
  .filter((num) => num > 3)
  .map((num) => num * 2);
console.log(filtered); // [8, 10]

filter - 条件に合う要素を抽出する

filterの基本構文

filterメソッドは、コールバック関数がtrueを返す要素だけを抽出して新しい配列を作成します。

1
2
const newArray = array.filter(callbackFn);
const newArray = array.filter(callbackFn, thisArg);

コールバック関数はmapと同じ引数を受け取りますが、戻り値は真偽値(truthy/falsy)である必要があります。

filterの基本的な使い方

条件に合う要素だけを取り出す場合に使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const scores = [85, 42, 93, 68, 55, 77];

// 70点以上のスコアだけを抽出
const passed = scores.filter((score) => score >= 70);
console.log(passed); // [85, 93, 77]

// 偶数だけを抽出
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = numbers.filter((num) => num % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

オブジェクト配列のフィルタリング

オブジェクトの配列から条件に合うものだけを抽出する実践的な例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const products = [
  { name: "りんご", price: 150, category: "果物", inStock: true },
  { name: "パン", price: 200, category: "パン", inStock: false },
  { name: "バナナ", price: 100, category: "果物", inStock: true },
  { name: "牛乳", price: 180, category: "飲料", inStock: true }
];

// 果物カテゴリの商品だけを抽出
const fruits = products.filter((product) => product.category === "果物");
console.log(fruits);
// [
//   { name: "りんご", price: 150, category: "果物", inStock: true },
//   { name: "バナナ", price: 100, category: "果物", inStock: true }
// ]

// 在庫があり、200円未満の商品を抽出
const available = products.filter(
  (product) => product.inStock && product.price < 200
);
console.log(available);
// [
//   { name: "りんご", price: 150, category: "果物", inStock: true },
//   { name: "バナナ", price: 100, category: "果物", inStock: true },
//   { name: "牛乳", price: 180, category: "飲料", inStock: true }
// ]

filterで重複を除去する

filterとインデックスを組み合わせて、配列から重複を除去することもできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const numbers = [1, 2, 2, 3, 3, 3, 4, 5, 5];

// 最初に出現した要素だけを残す
const unique = numbers.filter((value, index, self) => {
  return self.indexOf(value) === index;
});
console.log(unique); // [1, 2, 3, 4, 5]

// より簡潔な方法:Setを使用
const uniqueWithSet = [...new Set(numbers)];
console.log(uniqueWithSet); // [1, 2, 3, 4, 5]

filterの注意点

filterは条件に合う要素がない場合、空の配列を返します。これはエラーではなく正常な動作です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const numbers = [1, 2, 3, 4, 5];

// 条件に合う要素がない場合
const result = numbers.filter((num) => num > 10);
console.log(result);        // []
console.log(result.length); // 0

// 空配列でもチェーンは継続できる
const doubled = result.map((num) => num * 2);
console.log(doubled); // []

reduce - 配列を単一の値に集約する

reduceの基本構文

reduceメソッドは、配列の各要素に対してコールバック関数を実行し、単一の値に集約します。最も強力かつ柔軟な配列メソッドですが、理解するのに時間がかかることもあります。

1
const result = array.reduce(callbackFn, initialValue);

コールバック関数は以下の引数を受け取ります。

引数 説明
accumulator 累積値(前回のコールバックの戻り値)
currentValue 現在処理中の要素
currentIndex 現在の要素のインデックス(省略可能)
array reduceが呼び出された配列自体(省略可能)

第2引数のinitialValueaccumulatorの初期値です。

reduceの動作を図解する

reduceの動作を理解するために、処理の流れを図で確認しましょう。

flowchart LR
    subgraph "reduce の処理フロー"
        A["初期値: 0"] --> B["1回目: 0 + 1 = 1"]
        B --> C["2回目: 1 + 2 = 3"]
        C --> D["3回目: 3 + 3 = 6"]
        D --> E["4回目: 6 + 4 = 10"]
        E --> F["最終結果: 10"]
    end
    
    style A fill:#e2e8f0,stroke:#64748b
    style F fill:#4ade80,stroke:#22c55e

合計値を計算する

最も基本的な使い方は、配列の要素を合計することです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const numbers = [1, 2, 3, 4, 5];

// 合計値を計算
const sum = numbers.reduce((accumulator, current) => {
  return accumulator + current;
}, 0);
console.log(sum); // 15

// アロー関数の省略記法
const sumShort = numbers.reduce((acc, cur) => acc + cur, 0);
console.log(sumShort); // 15

処理の流れを詳しく見てみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const numbers = [1, 2, 3, 4, 5];

const sum = numbers.reduce((accumulator, current, index) => {
  console.log(`${index}回目: accumulator=${accumulator}, current=${current}`);
  return accumulator + current;
}, 0);

// 出力:
// 0回目: accumulator=0, current=1
// 1回目: accumulator=1, current=2
// 2回目: accumulator=3, current=3
// 3回目: accumulator=6, current=4
// 4回目: accumulator=10, current=5
// sum = 15

オブジェクトへの集約

reduceは数値の合計だけでなく、配列をオブジェクトに変換する場合にも使用できます。

1
2
3
4
5
6
7
8
9
const fruits = ["りんご", "バナナ", "りんご", "オレンジ", "バナナ", "りんご"];

// 各要素の出現回数をカウント
const count = fruits.reduce((acc, fruit) => {
  acc[fruit] = (acc[fruit] || 0) + 1;
  return acc;
}, {});
console.log(count);
// { りんご: 3, バナナ: 2, オレンジ: 1 }

配列のフラット化

ネストした配列を1次元配列に変換することもできます。

1
2
3
4
5
6
7
8
9
const nested = [[1, 2], [3, 4], [5, 6]];

// reduceで配列をフラット化
const flattened = nested.reduce((acc, arr) => acc.concat(arr), []);
console.log(flattened); // [1, 2, 3, 4, 5, 6]

// ES2019以降はflat()メソッドを使用(推奨)
const flattenedModern = nested.flat();
console.log(flattenedModern); // [1, 2, 3, 4, 5, 6]

最大値・最小値を求める

配列から最大値や最小値を求めることもできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const numbers = [5, 2, 9, 1, 7, 3];

// 最大値を求める
const max = numbers.reduce((acc, cur) => Math.max(acc, cur));
console.log(max); // 9

// 最小値を求める
const min = numbers.reduce((acc, cur) => Math.min(acc, cur));
console.log(min); // 1

// Math.max/minを直接使う方法(推奨)
console.log(Math.max(...numbers)); // 9
console.log(Math.min(...numbers)); // 1

reduceの注意点

reduceは強力ですが、以下の点に注意が必要です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 注意1: 空配列で初期値がないとエラー
const empty = [];
// empty.reduce((acc, cur) => acc + cur); // TypeError!
const safeSum = empty.reduce((acc, cur) => acc + cur, 0); // 0

// 注意2: 初期値を省略すると最初の要素が初期値になる
const numbers = [1, 2, 3];
const sum1 = numbers.reduce((acc, cur) => acc + cur);    // 6(初期値なし)
const sum2 = numbers.reduce((acc, cur) => acc + cur, 0); // 6(初期値あり)

// オブジェクト配列の場合は必ず初期値が必要
const users = [{ value: 10 }, { value: 20 }, { value: 30 }];
const total = users.reduce((acc, user) => acc + user.value, 0); // 60

reduceを使うべき場面と避けるべき場面

reduceは万能ですが、可読性の観点から他のメソッドが適している場合もあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const numbers = [1, 2, 3, 4, 5];

// reduceより適切なメソッドがある例

// 配列のフラット化 → flat()を使用
const nested = [[1, 2], [3, 4]];
const flat = nested.flat(); // nested.reduce((a, b) => a.concat(b), []) より簡潔

// 重複除去 → Setを使用
const duplicates = [1, 2, 2, 3];
const unique = [...new Set(duplicates)]; // reduceより簡潔

// 条件に合う要素を探す → find()を使用
const found = numbers.find((n) => n > 3); // reduceより明確

// reduceが適切な場面
// - 合計・平均の計算
// - オブジェクトへの変換(グループ化、カウントなど)
// - 複数の値を1つにまとめる処理

3つのメソッドの使い分け

比較表

mapfilterreduceの違いを表にまとめます。

メソッド 戻り値 主な用途 配列の長さ
map 新しい配列 各要素を変換する 元と同じ
filter 新しい配列 条件に合う要素を抽出する 元と同じか少ない
reduce 任意の値 配列を集約する 単一の値になる

選び方のフローチャート

どのメソッドを使うべきか迷ったときは、以下のフローチャートを参考にしてください。

flowchart TD
    A["配列を処理したい"] --> B{"結果は配列?"}
    B -->|はい| C{"要素数は変わる?"}
    B -->|いいえ| D["reduce を使う"]
    C -->|いいえ| E["map を使う"]
    C -->|はい| F{"要素を削除する?"}
    F -->|はい| G["filter を使う"]
    F -->|いいえ| H["flatMap を検討"]
    
    style D fill:#fbbf24,stroke:#f59e0b
    style E fill:#60a5fa,stroke:#3b82f6
    style G fill:#4ade80,stroke:#22c55e

メソッドチェーンで組み合わせる

基本的なメソッドチェーン

mapfilterreduceは連結(チェーン)して使用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const products = [
  { name: "りんご", price: 150, category: "果物" },
  { name: "パン", price: 200, category: "パン" },
  { name: "バナナ", price: 100, category: "果物" },
  { name: "牛乳", price: 180, category: "飲料" },
  { name: "オレンジ", price: 120, category: "果物" }
];

// 果物の合計金額を計算
const fruitTotal = products
  .filter((product) => product.category === "果物")  // 果物だけ抽出
  .map((product) => product.price)                   // 価格だけ取り出す
  .reduce((sum, price) => sum + price, 0);           // 合計を計算

console.log(fruitTotal); // 370

実践例:売上データの分析

より実践的な例として、売上データを分析するコードを見てみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const sales = [
  { product: "りんご", quantity: 10, unitPrice: 150, date: "2026-01-01" },
  { product: "バナナ", quantity: 5, unitPrice: 100, date: "2026-01-01" },
  { product: "りんご", quantity: 8, unitPrice: 150, date: "2026-01-02" },
  { product: "オレンジ", quantity: 12, unitPrice: 120, date: "2026-01-02" },
  { product: "バナナ", quantity: 7, unitPrice: 100, date: "2026-01-03" }
];

// 1. 各売上の金額を計算して追加
const salesWithTotal = sales.map((sale) => ({
  ...sale,
  total: sale.quantity * sale.unitPrice
}));
console.log(salesWithTotal);

// 2. 1000円以上の売上だけを抽出
const largeSales = salesWithTotal.filter((sale) => sale.total >= 1000);
console.log(largeSales);

// 3. 商品別の売上合計を集計
const salesByProduct = sales.reduce((acc, sale) => {
  const total = sale.quantity * sale.unitPrice;
  acc[sale.product] = (acc[sale.product] || 0) + total;
  return acc;
}, {});
console.log(salesByProduct);
// { りんご: 2700, バナナ: 1200, オレンジ: 1440 }

パフォーマンスと可読性のバランス

チェーンの回数に注意する

メソッドチェーンは便利ですが、配列が大きい場合はパフォーマンスに影響することがあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const largeArray = Array.from({ length: 100000 }, (_, i) => i);

// 悪い例:配列を3回走査する
const result1 = largeArray
  .filter((n) => n % 2 === 0)
  .map((n) => n * 2)
  .reduce((sum, n) => sum + n, 0);

// 良い例:reduceで1回の走査にまとめる
const result2 = largeArray.reduce((sum, n) => {
  if (n % 2 === 0) {
    return sum + n * 2;
  }
  return sum;
}, 0);

ただし、通常のWebアプリケーションでは可読性を優先し、パフォーマンス問題が発生した場合のみ最適化を検討することをおすすめします。

可読性を保つためのポイント

複雑な処理は関数に分割し、意図が明確になるようにしましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const products = [
  { name: "りんご", price: 150, category: "果物", inStock: true },
  { name: "パン", price: 200, category: "パン", inStock: false },
  { name: "バナナ", price: 100, category: "果物", inStock: true }
];

// 処理を関数に分割して可読性を向上
const isFruit = (product) => product.category === "果物";
const isInStock = (product) => product.inStock;
const getPrice = (product) => product.price;
const sum = (a, b) => a + b;

// 意図が明確なチェーン
const fruitTotal = products
  .filter(isFruit)
  .filter(isInStock)
  .map(getPrice)
  .reduce(sum, 0);

console.log(fruitTotal); // 250

まとめ

本記事では、JavaScriptの高階関数であるmapfilterreduceについて解説しました。

  • map: 配列の各要素を変換して新しい配列を作成する
  • filter: 条件に合う要素だけを抽出して新しい配列を作成する
  • reduce: 配列の要素を単一の値に集約する

これらのメソッドを組み合わせることで、複雑なデータ処理も簡潔に記述できます。最初はforループのほうが書きやすく感じるかもしれませんが、慣れてくると高階関数を使ったコードのほうが意図が明確で保守しやすいことがわかるでしょう。

実際のプロジェクトで積極的に使い、徐々に使い分けの感覚を身につけていくことをおすすめします。

参考リンク