配列の基本とよく使うメソッド — JavaScript Tips
配列では map / filter / reduce などで変換・絞り込み・集計ができます。あわせて、元の配列を変えるメソッド と 新しい配列を返すメソッド を分けて考えます。
参考: Array · Array.prototype.map() · Array.prototype.filter() · Array.prototype.reduce()
目次
- 配列の基本操作
- 変換する:map
- 絞り込む:filter
- 集計する:reduce
- 検索する:find / findIndex / includes
- 条件を調べる:some / every
- 元の配列を変えるメソッド
- うまくいかないとき
配列の基本操作
配列は順序付きのリストです。インデックスは 0 から始まり、length で要素数を取得できます。
const fruits = ["りんご", "みかん", "ぶどう"];console.log(fruits[0]); // "りんご"console.log(fruits.length); // 3fruits.push("バナナ"); // 末尾に追加(元の配列を変更)console.log(fruits); // ["りんご", "みかん", "ぶどう", "バナナ"]push・pop・splice・sort などは 元の配列を変更(mutate) します。一方、map・filter・slice は 新しい配列を返す ので、元データを残したい処理に向いています。
変換する:map
map は各要素に関数を適用し、同じ長さの新しい配列 を返します。「一覧の表示用データを作る」「単位を変換する」といった用途で使います。
const prices = [100, 250, 80];// 税込み価格の配列を作るconst withTax = prices.map((price) => Math.floor(price * 1.1));console.log(withTax); // [110, 275, 88]console.log(prices); // [100, 250, 80] — 元は変わらない// オブジェクト配列から特定のプロパティだけ取り出すconst users = [ { id: 1, name: "田中" }, { id: 2, name: "佐藤" },];const names = users.map((user) => user.name);console.log(names); // ["田中", "佐藤"]map のコールバックには (要素, インデックス, 配列) が渡されます。インデックスが必要なときだけ第 2 引数を使いましょう。
絞り込む:filter
filter は条件を満たす要素だけを集めた 新しい配列 を返します。条件に合う要素がなければ空配列 [] になります。
const scores = [42, 88, 55, 91, 73];const passed = scores.filter((score) => score >= 60);console.log(passed); // [88, 91, 73]const products = [ { name: "ノート", stock: 0 }, { name: "ペン", stock: 12 }, { name: "消しゴム", stock: 5 },];const available = products.filter((p) => p.stock > 0);console.log(available.map((p) => p.name)); // ["ペン", "消しゴム"]filter のあとに map をつなぐパターンはよく使います。ただし配列を 2 回走査するので、要素数が非常に大きいときは reduce で 1 回にまとめる選択肢もあります。
集計する:reduce
reduce は配列を ひとつの値(数値・オブジェクト・配列など)に畳み込みます。合計・最大値・グループ化などに使えます。
const numbers = [3, 1, 4, 1, 5];const sum = numbers.reduce((acc, n) => acc + n, 0);console.log(sum); // 14const max = numbers.reduce((acc, n) => (n > acc ? n : acc), numbers[0]);console.log(max); // 5// カテゴリごとに件数を数えるconst items = [ { category: "果物", name: "りんご" }, { category: "野菜", name: "にんじん" }, { category: "果物", name: "みかん" },];const countByCategory = items.reduce((acc, item) => { acc[item.category] = (acc[item.category] ?? 0) + 1; return acc;}, {});console.log(countByCategory); // { 果物: 2, 野菜: 1 }第 2 引数(初期値)を省略すると、配列の先頭要素が初期値になります。空配列で省略するとエラーになるため、初期値は明示する と安全です。
検索する:find / findIndex / includes
| メソッド | 戻り値 | 用途 |
|---|---|---|
find |
条件に合う最初の要素(なければ undefined) |
オブジェクトの検索 |
findIndex |
条件に合う最初のインデックス(なければ -1) |
位置が必要なとき |
includes |
値が含まれるか true / false |
プリミティブの存在確認 |
const todos = [ { id: "a1", text: "買い物", done: false }, { id: "b2", text: "掃除", done: true },];const target = todos.find((todo) => todo.id === "b2");console.log(target?.text); // "掃除"const index = todos.findIndex((todo) => todo.done === false);console.log(index); // 0const tags = ["js", "web", "tips"];console.log(tags.includes("web")); // trueconsole.log(tags.includes("java")); // falseincludes は === で比較します。オブジェクトの「中身が同じか」は判定できないので、オブジェクト配列では find を使います。
条件を調べる:some / every
some… ひとつでも 条件を満たせばtrueevery… すべて 条件を満たせばtrue(空配列ではtrue)
const orders = [ { id: 1, paid: true }, { id: 2, paid: false }, { id: 3, paid: true },];const hasUnpaid = orders.some((o) => !o.paid);console.log(hasUnpaid); // trueconst allPaid = orders.every((o) => o.paid);console.log(allPaid); // falseconst empty = [];console.log(empty.every((x) => x > 100)); // true(要素がないので vacuously true)console.log(empty.some((x) => x > 100)); // falseフォームのバリデーションで「すべての必須項目が埋まっているか」を every で調べる、権限チェックで「管理者が一人でもいるか」を some で調べる、といった使い方が多いです。
元の配列を変えるメソッド
実務では「新しい配列を返す」メソッドと「元を変える」メソッドが混在します。代表例は次のとおりです。
新しい配列を返す(非破壊)
const original = [3, 1, 2];const sorted = [...original].sort((a, b) => a - b);console.log(sorted); // [1, 2, 3]console.log(original); // [3, 1, 2] — そのまま元の配列を変更する(破壊的)
const list = [1, 2, 3];list.reverse();console.log(list); // [3, 2, 1]| 非破壊的 | 破壊的 |
|---|---|
map, filter, slice, concat |
push, pop, shift, unshift |
toSorted()(ES2023) |
sort, reverse, splice |
共有している配列を意図せず書き換えるとバグになります。ソートや並べ替えの前に [...arr] や arr.slice() でコピーします。
うまくいかないとき
map の戻り値を捨てている
map は新しい配列を返すだけで、元の配列は変えません。副作用だけ実行したいなら forEach を使います。
// NG: 戻り値を使っていないusers.map((user) => { sendEmail(user); // 副作用だけ — map ではなく forEach が意図に合う});// OKusers.forEach((user) => sendEmail(user));filter 後に要素がないのにアクセスしている
const admins = users.filter((u) => u.role === "admin");const firstAdmin = admins[0]; // undefined の可能性if (firstAdmin) { // 処理}find を使えば「最初の 1 件」が目的のときに簡潔です。
reduce の初期値を忘れて NaN になる
// NG: 初期値なしで数値合計(先頭要素に依存)[].reduce((a, b) => a + b); // TypeError// OK[].reduce((a, b) => a + b, 0); // 0sort が文字列順になっている
sort() に比較関数を渡さないと、要素を 文字列に変換してから 並べます。数値の大小順にはなりません。
const nums = [10, 2, 9];// 文字列として比較: "10", "2", "9" → "10" < "2" < "9"nums.sort();console.log(nums); // [10, 2, 9](見た目は近いが、意図した数値順ではない)// 数値の昇順にするには比較関数を渡す[10, 2, 9].sort((a, b) => a - b); // [2, 9, 10]元の配列を残したいときは [...nums].sort((a, b) => a - b) のようにコピーしてから並べます。
チェーンが長すぎて読みにくい
filter → map → filter とつなぎすぎたら、途中結果を変数に分けるか reduce でまとめるとデバッグしやすくなります。
// 読みにくいconst result = items.filter((x) => x.active).map((x) => x.name).filter((n) => n.length > 0);// 段階を分けるconst active = items.filter((x) => x.active);const names = active.map((x) => x.name);const result = names.filter((n) => n.length > 0);