こんにちは、かつコーチです。
前回は配列の基本的な作成・追加・削除の方法を解説しました。
今回は一歩進んで、配列を「加工する」ときによく使う map ・ filter ・ reduce の3つのメソッドを解説します。
現場のコードでも頻出のメソッドなので、ここでしっかり使い方を押さえておきましょう。
map・filter・reduceとは?
forEachとの違い
forEach は配列の要素を1つずつ処理するだけで、新しい配列を返さない メソッドでした。
一方、map ・ filter ・ reduce は、元の配列をもとに 新しいデータを作って返す という共通点があります。
| メソッド | 何をするか | 戻り値 |
|---|---|---|
map | 各要素を変換する | 新しい配列 |
filter | 条件に合う要素だけ残す | 新しい配列 |
reduce | 全要素を1つの値にまとめる | 任意の値(数値・文字列・オブジェクトなど) |
なぜこの3つを覚えるべきなのか
for 文でも同じ処理は書けますが、map ・ filter ・ reduce を使うと 「何をしたいコードなのか」がひと目で伝わる という大きなメリットがあります。
「配列を変換したいなら map」「配列を絞り込みたいなら filter」というように、目的とメソッド名が対応しているため、コードの意図が読み取りやすくなります。
mapで配列を変換する
基本の書き方
map() は、配列の各要素に処理を行い、その結果を新しい配列として返します。
const numbers = [1, 2, 3, 4];
const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6, 8]
console.log(numbers); // [1, 2, 3, 4](元の配列は変わらない)
ポイントは、map() が 元の配列を変更せず、新しい配列を作って返す ということです。
オブジェクトの配列を加工する
実務では、数値の配列よりもオブジェクトの配列を加工する場面のほうが多いです。
const users = [
{ name: "田中", age: 20 },
{ name: "佐藤", age: 25 },
];
const names = users.map((user) => user.name);
console.log(names); // ["田中", "佐藤"]
「オブジェクトの配列から、特定のプロパティだけ取り出した配列を作りたい」というのは、APIから取得したデータを画面表示用に整形するときの定番パターンです。
filterで条件に合う要素だけ残す
基本の書き方
filter() は、条件(コールバック関数がtrueを返すもの)に合う要素だけを集めた新しい配列を返します。
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter((num) => num % 2 === 0);
console.log(evenNumbers); // [2, 4, 6]
コールバック関数が true を返した要素だけが新しい配列に残り、false を返した要素は除外されます。
オブジェクトの配列を絞り込む
filter もオブジェクトの配列と組み合わせるとさらに便利になります。
const users = [
{ name: "田中", age: 17 },
{ name: "佐藤", age: 25 },
{ name: "鈴木", age: 19 },
];
const adults = users.filter((user) => user.age >= 20);
console.log(adults); // [{ name: "佐藤", age: 25 }]
「条件に合うデータだけ表示したい」という検索・絞り込み機能は、filter の典型的な使いどころです。
reduceで配列を1つの値にまとめる
基本の書き方
reduce() は3つの中で一番つまずきやすいメソッドです。
配列のすべての要素を処理しながら、最終的に 1つの値 にまとめます。
const numbers = [1, 2, 3, 4];
const total = numbers.reduce((accumulator, current) => {
return accumulator + current;
}, 0); // 第2引数が初期値
console.log(total); // 10
accumulator(累積値)には前回のコールバックの戻り値が、current には現在処理中の要素が入ります。
初期値(第2引数の 0)から始まり、要素を1つずつ足しこんでいくイメージです。
つまずきやすいポイント:初期値を省略して混乱した話
私が reduce を初めて使ったとき、初期値(第2引数)を省略して意図しない結果になった ことがあります。
❌ Before:初期値を省略する
const users = [
{ name: "田中", age: 20 },
{ name: "佐藤", age: 25 },
{ name: "鈴木", age: 19 },
];
// 年齢の合計を出したいのに、初期値を省略してしまった
const totalAge = users.reduce((acc, user) => {
return acc + user.age;
});
// エラー: acc(1件目のオブジェクト)+ user.age(数値)になり、
// "[object Object]25" のような意図しない文字列結合や NaN が発生する
初期値を省略すると、reduce は 配列の0番目の要素をそのまま初期値として使ってしまいます。
このケースでは acc が最初「オブジェクト」になってしまうため、数値の合計を出すつもりが文字列結合のような挙動になり、原因が分からずかなり時間を溶かしました。
✅ After:必ず初期値を明示する
const users = [
{ name: "田中", age: 20 },
{ name: "佐藤", age: 25 },
{ name: "鈴木", age: 19 },
];
const totalAge = users.reduce((acc, user) => {
return acc + user.age;
}, 0); // 初期値を明示することで、accは常に「数値」になる
console.log(totalAge); // 64
reduce を使うときは、「初期値を必ず書く」 というルールを自分の中で徹底しておくと、この手のバグを未然に防げます。
応用:3つを組み合わせて使う
メソッドチェーンで処理をつなげる
map ・ filter ・ reduce はいずれも新しい配列(または値)を返すため、. でつなげて連続して呼び出す「メソッドチェーン」ができます。
const products = [
{ name: "ノートPC", price: 80000, inStock: true },
{ name: "マウス", price: 2000, inStock: false },
{ name: "キーボード", price: 5000, inStock: true },
];
// 在庫がある商品だけ抽出し、価格に10%の税を加えて、合計金額を求める
const totalPrice = products
.filter((product) => product.inStock)
.map((product) => product.price * 1.1)
.reduce((acc, price) => acc + price, 0);
console.log(Math.round(totalPrice)); // 93500
「絞り込む→加工する→まとめる」という一連の流れを、for 文を何個も書かずに1つの流れとして表現できるのが、この3つのメソッドを組み合わせる最大のメリットです。
最初は難しく感じても、1つずつ処理を分解して読む癖をつければ、必ず読めるようになります。
まとめ
この記事のポイント
mapは配列を変換して新しい配列を返すfilterは条件に合う要素だけを残した新しい配列を返すreduceは配列を1つの値にまとめる。初期値(第2引数)を省略すると意図しない挙動になりやすい- 3つとも元の配列は変更せず、新しいデータを返す
- メソッドチェーンで組み合わせると、複雑な処理も読みやすく書ける
次に読むべき記事
配列の操作ができるようになったら、次はもう1つの重要なデータ構造「オブジェクト」を見ていきましょう。
→ 次の記事:オブジェクトの基本