JavaScript/TypeScript

配列の基本とよく使うメソッド

5
  • JavaScript

配列の基本とよく使うメソッド — JavaScript Tips

配列では map / filter / reduce などで変換・絞り込み・集計ができます。あわせて、元の配列を変えるメソッド新しい配列を返すメソッド を分けて考えます。

参考: Array · Array.prototype.map() · Array.prototype.filter() · Array.prototype.reduce()


目次

  1. 配列の基本操作
  2. 変換する:map
  3. 絞り込む:filter
  4. 集計する:reduce
  5. 検索する:find / findIndex / includes
  6. 条件を調べる:some / every
  7. 元の配列を変えるメソッド
  8. うまくいかないとき

配列の基本操作

配列は順序付きのリストです。インデックスは 0 から始まり、length で要素数を取得できます。

const fruits = ["りんご", "みかん", "ぶどう"];console.log(fruits[0]);      // "りんご"console.log(fruits.length);  // 3fruits.push("バナナ");       // 末尾に追加(元の配列を変更)console.log(fruits);         // ["りんご", "みかん", "ぶどう", "バナナ"]

pushpopsplicesort などは 元の配列を変更(mutate) します。一方、mapfilterslice新しい配列を返す ので、元データを残したい処理に向いています。


変換する: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")); // false

includes=== で比較します。オブジェクトの「中身が同じか」は判定できないので、オブジェクト配列では find を使います。


条件を調べる:some / every

  • someひとつでも 条件を満たせば true
  • everyすべて 条件を満たせば 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); // 0

sort が文字列順になっている

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) のようにコピーしてから並べます。

チェーンが長すぎて読みにくい

filtermapfilter とつなぎすぎたら、途中結果を変数に分けるか 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);

シェア