JavaScript の配列メソッドは、フロントエンド・Node.js・TypeScript いずれの現場でも毎日触る最重要 API です。とはいえ map / filter / reduce までは書けても、findLast / toSorted / toReversed / with / Object.groupBy / Array.fromAsync といった ES2023〜2024 で追加された新 Immutable 系メソッドを実務で使いこなしている人は意外と少ないのが現状です。
本記事では ECMAScript 2025 / Node.js 22+ / TypeScript 5.x 準拠で、コピペで動く 40 以上のコードサンプルを通して、破壊的 vs 非破壊的メソッドの違い → 検索・走査 → 変換系(map / filter / reduce) → ES2023 新 Immutable メソッド → ES2024 Object.groupBy / Array.fromAsync → chunk / unique / partition などの実用パターン → パフォーマンス比較 → TypeScript 型推論まで、配列メソッド単体としての実践知見を完全網羅します。
本記事は「配列操作」観点に特化しているため、JavaScript ベストプラクティス 10 選(言語全般の観点)、および async/await 完全実践ガイド(非同期処理の観点)と相互補完的に読むと、JavaScript の基礎体力が一気に上がります。
- 1. 配列メソッドの全体像 – 破壊的 vs 非破壊的
- 2. 末尾・先頭の追加削除 – push / pop / shift / unshift
- 3. 範囲操作 – slice / splice
- 4. 結合・分割 – concat / spread / join / split
- 5. 検索 – indexOf / includes / find / findIndex
- 6. 走査・判定 – forEach / some / every
- 7. 変換 – map / filter
- 8. 集約 – reduce / reduceRight
- 9. ネスト処理 – flat / flatMap
- 10. ソート – sort / toSorted
- 11. ES2023 新メソッド – with でピンポイント書き換え
- 12. 配列生成 – Array.from / Array.of / fromAsync
- 13. グルーピング – Object.groupBy / Map.groupBy (ES2024)
- 14. 実用パターン集 – chunk / unique / partition / zip
- 15. パイプライン処理 – チェイン記法
- 16. パフォーマンス比較 – for / forEach / map / reduce
- 17. TypeScript の型推論
- 18. Generator → 配列化
- 19. structuredClone で深いコピー
- 20. よくある落とし穴とアンチパターン
- 21. まとめ – 配列メソッド習得チェックリスト
1. 配列メソッドの全体像 – 破壊的 vs 非破壊的
配列メソッドを語る上で最重要の区別が「破壊的(mutating)」と「非破壊的(non-mutating)」です。破壊的メソッドは元配列を書き換え、非破壊的メソッドは新しい配列を返します。ES2023 で toSorted / toReversed / toSpliced / with というImmutable 版が追加され、関数型スタイルが標準で書けるようになりました。
1.1 破壊的メソッド一覧
以下のメソッドは元配列を書き換えるため、React の state や Redux の store でそのまま使うと再レンダーされない / Immer なしでは事故る原因になります。
// 破壊的メソッド(元配列を変更する)
// push / pop / shift / unshift / splice / sort / reverse / fill / copyWithin
const arr = [3, 1, 4, 1, 5];
arr.sort(); // arr 自体が [1, 1, 3, 4, 5] に書き換わる
arr.reverse(); // arr 自体が逆順に書き換わる
arr.push(9); // arr の末尾に 9 が追加される
console.log(arr); // [5, 4, 3, 1, 1, 9]
1.2 非破壊的メソッド一覧
こちらは元配列を変更せず新しい配列を返すため、React / Redux / Vue / Svelte いずれの状態管理でも安全に使えます。
// 非破壊的メソッド(新配列を返す)
// slice / concat / map / filter / flat / flatMap
// toSorted / toReversed / toSpliced / with(ES2023)
const original = [3, 1, 4, 1, 5];
const sorted = original.toSorted(); // [1, 1, 3, 4, 5]
console.log(original); // [3, 1, 4, 1, 5] ← 元のまま!
console.log(sorted); // [1, 1, 3, 4, 5]
1.3 早見表で覚える対応関係
ES2023 の Immutable メソッドは「破壊的メソッド名の頭に to を付ける」命名規則で覚えると一発で身につきます。
// 破壊的 → 非破壊的(ES2023)
// sort() → toSorted()
// reverse() → toReversed()
// splice() → toSpliced()
// arr[i]=v → with(i, v)
const arr = [1, 2, 3];
const newArr = arr.with(1, 99); // [1, 99, 3]
console.log(arr); // [1, 2, 3] ← 不変
2. 末尾・先頭の追加削除 – push / pop / shift / unshift
もっとも基礎的な配列操作です。すべて破壊的かつ shift / unshift は O(n) のコストがかかる点に注意します。
2.1 push / pop で末尾を出し入れ
末尾操作は O(1) で高速。スタック構造(LIFO)として使えます。
const stack = [];
stack.push("a"); // ["a"]
stack.push("b"); // ["a", "b"]
stack.push("c"); // ["a", "b", "c"]
const last = stack.pop(); // last = "c", stack = ["a", "b"]
console.log(stack.length); // 2
// push は複数引数 OK / 戻り値は新しい length
const len = stack.push("x", "y", "z");
console.log(len); // 5
2.2 shift / unshift で先頭を出し入れ
先頭操作はインデックスのずらしが必要なため O(n)。大量データではキュー専用ライブラリ(denque 等)の利用を検討します。
const queue = [1, 2, 3];
const first = queue.shift(); // first = 1, queue = [2, 3]
queue.unshift(0); // queue = [0, 2, 3]
// FIFO(キュー)実装
const tasks = ["task1", "task2", "task3"];
while (tasks.length > 0) {
const task = tasks.shift(); // 先頭から取り出す
console.log("実行:", task);
}
2.3 イミュータブルに先頭・末尾を追加する
React / Redux では破壊的メソッドではなくspread 構文で新配列を作るのが鉄則です。
const items = [1, 2, 3];
// 末尾追加(push の代わり)
const added = [...items, 4]; // [1, 2, 3, 4]
// 先頭追加(unshift の代わり)
const prepended = [0, ...items]; // [0, 1, 2, 3]
// 末尾削除(pop の代わり)
const popped = items.slice(0, -1); // [1, 2]
// 先頭削除(shift の代わり)
const shifted = items.slice(1); // [2, 3]
3. 範囲操作 – slice / splice
slice と splice は名前が似ていますが、slice は非破壊・splice は破壊と真逆の性質を持つので必ず使い分けます。
3.1 slice – 部分配列を切り出す(非破壊)
slice(start, end) は end を含まない(half-open)点に注意します。負のインデックスで末尾基準も指定可。
const arr = [10, 20, 30, 40, 50];
arr.slice(1, 3); // [20, 30] (index 1 から index 3 の手前まで)
arr.slice(2); // [30, 40, 50] (end 省略 = 最後まで)
arr.slice(-2); // [40, 50] (末尾 2 つ)
arr.slice(-3, -1); // [30, 40]
arr.slice(); // 全要素コピー(浅いコピー)
console.log(arr); // [10, 20, 30, 40, 50] ← 不変!
3.2 splice – 削除・置換・挿入(破壊的)
splice(start, deleteCount, ...items) は削除・置換・挿入を 1 つでこなす万能関数。戻り値は削除された要素の配列です。
// 削除
const a = [1, 2, 3, 4, 5];
const removed = a.splice(1, 2); // index 1 から 2 個削除
console.log(removed); // [2, 3]
console.log(a); // [1, 4, 5]
// 挿入(deleteCount = 0)
const b = [1, 4, 5];
b.splice(1, 0, 2, 3); // index 1 に 2, 3 を挿入
console.log(b); // [1, 2, 3, 4, 5]
// 置換
const c = ["a", "b", "c"];
c.splice(1, 1, "B1", "B2"); // "b" を 2 要素で置換
console.log(c); // ["a", "B1", "B2", "c"]
3.3 toSpliced – splice の非破壊版(ES2023)
ES2023 で追加された toSpliced は splice の Immutable 版。戻り値が「変更後の新しい配列」になる(削除要素ではない)点が splice と異なります。
const arr = [1, 2, 3, 4, 5];
// 元配列を変えずに index 1 から 2 個削除した新配列
const result = arr.toSpliced(1, 2);
console.log(result); // [1, 4, 5]
console.log(arr); // [1, 2, 3, 4, 5] ← 不変
// 挿入も可
arr.toSpliced(1, 0, 99); // [1, 99, 2, 3, 4, 5]
// 置換も可
arr.toSpliced(1, 2, "x", "y", "z"); // [1, "x", "y", "z", 4, 5]
4. 結合・分割 – concat / spread / join / split
4.1 concat と spread 構文
配列同士の連結は concat でも spread 構文でも可能ですが、現代では spread が事実上の標準です。可読性が高く、複数配列の混在もシンプルに書けます。
const a = [1, 2];
const b = [3, 4];
const c = [5, 6];
// concat 方式
const r1 = a.concat(b, c); // [1, 2, 3, 4, 5, 6]
// spread 方式(推奨)
const r2 = [...a, ...b, ...c]; // [1, 2, 3, 4, 5, 6]
// spread は途中に値を挟める
const r3 = [0, ...a, 99, ...b]; // [0, 1, 2, 99, 3, 4]
// オブジェクト配列でも同様
const users1 = [{ id: 1 }];
const users2 = [{ id: 2 }];
const all = [...users1, ...users2]; // [{id:1}, {id:2}]
4.2 join – 配列を文字列に
join(separator) はセパレータで区切って結合します。CSV 出力やパンくずリスト生成などで頻出します。
["a", "b", "c"].join(); // "a,b,c" (デフォルトは ",")
["a", "b", "c"].join(""); // "abc"
["a", "b", "c"].join(" / "); // "a / b / c"
// パンくずリスト
const breadcrumb = ["Home", "Products", "Shoes", "Sneakers"];
console.log(breadcrumb.join(" > "));
// → "Home > Products > Shoes > Sneakers"
// null / undefined は空文字になる落とし穴
[1, null, 3, undefined, 5].join("-"); // "1--3--5"
4.3 split – 文字列を配列に
split は String 側のメソッドですが、配列メソッドと併用するパターンが多いのでセットで押さえます。
"a,b,c".split(","); // ["a", "b", "c"]
"hello".split(""); // ["h", "e", "l", "l", "o"]
"2026-05-27".split("-"); // ["2026", "05", "27"]
// 正規表現で柔軟に分割
"foo bar baz".split(/s+/); // ["foo", "bar", "baz"]
// 上限指定
"a,b,c,d".split(",", 2); // ["a", "b"]
// Unicode 対応で「文字単位」に分割するなら Array.from
Array.from("👨👩👧👦テスト");
// ["👨👩👧👦", "テ", "ス", "ト"] (絵文字 1 つを 1 要素として扱える)
5. 検索 – indexOf / includes / find / findIndex
配列内検索のメソッドは複数あり、「値を探す」か「条件で探す」か「インデックスが欲しいか値が欲しいか」で使い分けます。
5.1 indexOf / lastIndexOf / includes
「特定の値」を探すメソッドです。厳密等価(===)で比較するため、オブジェクトの検索には不向きです。
const arr = ["a", "b", "c", "b", "a"];
arr.indexOf("b"); // 1 (最初に見つかった位置)
arr.lastIndexOf("b"); // 3 (後ろから探す)
arr.indexOf("z"); // -1 (見つからない)
// includes は「存在するか」だけ知りたい時に
arr.includes("a"); // true
arr.includes("z"); // false
// indexOf は NaN を検出できない / includes はできる
[NaN].indexOf(NaN); // -1 ← 罠!
[NaN].includes(NaN); // true ← OK
5.2 find / findIndex – 条件で探す
オブジェクト配列など「条件マッチ」で検索する場合は find / findIndex を使います。見つからない場合は undefined / -1 を返します。
const users = [
{ id: 1, name: "Alice", age: 28 },
{ id: 2, name: "Bob", age: 34 },
{ id: 3, name: "Carol", age: 22 },
];
// 条件マッチした「要素」が欲しい
const user = users.find((u) => u.id === 2);
console.log(user); // { id: 2, name: "Bob", age: 34 }
// 条件マッチした「インデックス」が欲しい
const idx = users.findIndex((u) => u.name === "Carol");
console.log(idx); // 2
// 見つからない場合
users.find((u) => u.id === 99); // undefined
users.findIndex((u) => u.id === 99); // -1
5.3 findLast / findLastIndex (ES2023)
ES2023 で追加。後ろから検索する場合、従来は [...arr].reverse().find(...) のような書き方が必要でしたが、これらのメソッドで一発で書けるようになりました。
const logs = [
{ level: "info", msg: "起動" },
{ level: "error", msg: "DB 接続失敗" },
{ level: "info", msg: "リトライ" },
{ level: "error", msg: "再失敗" },
{ level: "info", msg: "終了" },
];
// 最新のエラーログを取りたい
const lastError = logs.findLast((l) => l.level === "error");
console.log(lastError); // { level: "error", msg: "再失敗" }
const lastErrorIdx = logs.findLastIndex((l) => l.level === "error");
console.log(lastErrorIdx); // 3
6. 走査・判定 – forEach / some / every
6.1 forEach の注意点 – break / return / await が効かない
forEach は「途中で抜けられない」「戻り値がない」「async と相性が悪い」3 大落とし穴を持ちます。多くの場合は for...of や map / filter に置き換えるべきです。
// ❌ break で抜けられない
[1, 2, 3].forEach((n) => {
if (n === 2) break; // SyntaxError!
});
// ❌ return しても次のループは止まらない
[1, 2, 3].forEach((n) => {
if (n === 2) return; // この関数を抜けるだけ。next iteration へ進む
console.log(n);
});
// → 1, 3 (2 はスキップだが、ループは続く)
// ❌ await が効かない(forEach のコールバックは並列実行)
[1, 2, 3].forEach(async (n) => {
await fetch(`/api/${n}`); // 順番待ちにならない
});
// ✅ 順序保証したいなら for...of
for (const n of [1, 2, 3]) {
await fetch(`/api/${n}`); // ちゃんと順番に待つ
}
6.2 some – 1 つでも条件マッチがあるか
some は短絡評価(true が出た時点で打ち切り)するため、大量配列の存在チェックに有効です。
const cart = [
{ name: "本", price: 1200 },
{ name: "ペン", price: 200 },
{ name: "ノート", price: 800 },
];
// 1 つでも 1000 円以上の商品はあるか
const hasExpensive = cart.some((item) => item.price >= 1000);
console.log(hasExpensive); // true
// バリデーション: 1 つでも不正な値があれば true
const inputs = ["", "valid", "OK"];
const hasEmpty = inputs.some((s) => s.length === 0);
console.log(hasEmpty); // true
6.3 every – 全要素が条件を満たすか
every も短絡評価で、false が出た時点で停止します。全件バリデーションに頻出。
const ages = [25, 30, 18, 22];
// 全員 20 歳以上か
const allAdults = ages.every((a) => a >= 20);
console.log(allAdults); // false (18 がいる)
// 空配列に対する every は常に true("vacuous truth")
console.log([].every((x) => x > 100)); // true
console.log([].some((x) => x > 100)); // false
// 必須項目 全入力チェック
const form = { name: "山田", email: "a@b.c", age: 30 };
const required = ["name", "email", "age"];
const isValid = required.every((key) => form[key] != null && form[key] !== "");
console.log(isValid); // true
7. 変換 – map / filter
7.1 map – 各要素を変換
map は配列の各要素を変換して新しい配列を返します。要素数は必ず同じ。React のリストレンダリングの定番です。
const nums = [1, 2, 3, 4, 5];
// 2 倍
const doubled = nums.map((n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// オブジェクト変換
const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
const names = users.map((u) => u.name);
console.log(names); // ["Alice", "Bob"]
// React の典型例
const ListView = ({ items }) => (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
// インデックスも使える
nums.map((n, i) => `${i}: ${n}`); // ["0: 1", "1: 2", ...]
7.2 filter – 条件で抽出
filter はtruthy を返す要素だけを残します。要素数は減ります。
const products = [
{ name: "A", price: 100, stock: 10 },
{ name: "B", price: 500, stock: 0 },
{ name: "C", price: 200, stock: 5 },
{ name: "D", price: 700, stock: 0 },
];
// 在庫ありの商品だけ
const inStock = products.filter((p) => p.stock > 0);
console.log(inStock); // A, C
// falsy 除去の鉄板パターン
const messy = [1, null, 2, undefined, 0, 3, "", "ok"];
const clean = messy.filter(Boolean);
console.log(clean); // [1, 2, 3, "ok"]
// TypeScript の型ガード
const mixed: (string | null)[] = ["a", null, "b"];
const onlyStr: string[] = mixed.filter((s): s is string => s !== null);
7.3 map + filter の合体 – flatMap
flatMap は map の結果を 1 段階フラット化します。「ある要素から 0〜N 個の要素を生成する」用途に最適で、map + filter を 1 度の走査で書けます。
// map + filter を flatMap で 1 行に
const nums = [1, 2, 3, 4, 5];
const result = nums.flatMap((n) => (n % 2 === 0 ? [n * 10] : []));
console.log(result); // [20, 40]
// 文字列をすべて単語に展開
const sentences = ["I love JS", "TypeScript is great"];
const words = sentences.flatMap((s) => s.split(" "));
console.log(words); // ["I", "love", "JS", "TypeScript", "is", "great"]
8. 集約 – reduce / reduceRight
reduce は配列を 1 つの値に畳み込む万能メソッド。合計・最大値・グルーピング・パイプラインなど、ありとあらゆる集約処理に使えます。
8.1 reduce の基本 – 合計 / 平均
第 2 引数の初期値は省略しないのがベストプラクティス。空配列で例外が出るのを防げます。
const prices = [120, 380, 250, 800, 150];
// 合計
const total = prices.reduce((sum, p) => sum + p, 0);
console.log(total); // 1700
// 平均
const avg = prices.reduce((s, p) => s + p, 0) / prices.length;
console.log(avg); // 340
// 最大値(Math.max スプレッドより堅牢)
const max = prices.reduce((m, p) => (p > m ? p : m), -Infinity);
console.log(max); // 800
// 最小値
const min = prices.reduce((m, p) => (p < m ? p : m), Infinity);
console.log(min); // 120
8.2 reduce でオブジェクトに集計
「カウント」「集計テーブル化」など、reduce はオブジェクトに畳み込むパターンが非常に多いです。
const fruits = ["apple", "banana", "apple", "orange", "banana", "apple"];
// カウント
const count = fruits.reduce((acc, f) => {
acc[f] = (acc[f] ?? 0) + 1;
return acc;
}, {});
console.log(count); // { apple: 3, banana: 2, orange: 1 }
// 配列をマップ化(key 化)
const users = [
{ id: "u1", name: "Alice" },
{ id: "u2", name: "Bob" },
{ id: "u3", name: "Carol" },
];
const userMap = users.reduce((acc, u) => {
acc[u.id] = u;
return acc;
}, {});
console.log(userMap.u2); // { id: "u2", name: "Bob" }
8.3 reduceRight – 後ろから畳み込む
関数合成や RTL(right-to-left)処理に使います。compose 関数の実装が典型例。
// 関数合成(compose): f(g(h(x)))
const compose = (...fns) =>
(x) => fns.reduceRight((acc, fn) => fn(acc), x);
const add1 = (x) => x + 1;
const double = (x) => x * 2;
const square = (x) => x * x;
const f = compose(add1, double, square);
console.log(f(3)); // square → double → add1 = 3² × 2 + 1 = 19
// 通常の reduce 版 pipe(LTR)
const pipe = (...fns) =>
(x) => fns.reduce((acc, fn) => fn(acc), x);
9. ネスト処理 – flat / flatMap
9.1 flat – 入れ子をフラット化
depth 引数で何階層までフラットにするかを指定可。Infinity を渡せば完全フラット化できます。
const nested = [1, [2, 3], [4, [5, 6]], [[7, [8]]]];
nested.flat(); // [1, 2, 3, 4, [5, 6], [7, [8]]]
nested.flat(2); // [1, 2, 3, 4, 5, 6, 7, [8]]
nested.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8]
// 空要素も除去される(sparse array に強い)
[1, , 2, , 3].flat(); // [1, 2, 3]
9.2 flatMap の実用例 – タグの集約
記事ごとに複数タグがある場合、flatMap でタグ一覧を一気に集約できます。
const posts = [
{ title: "JS 入門", tags: ["js", "beginner"] },
{ title: "React フック", tags: ["react", "hooks", "js"] },
{ title: "TS の型", tags: ["ts", "type"] },
];
// 全タグを 1 次元配列で取得
const allTags = posts.flatMap((p) => p.tags);
console.log(allTags);
// ["js", "beginner", "react", "hooks", "js", "ts", "type"]
// 重複除去まで
const uniqueTags = [...new Set(posts.flatMap((p) => p.tags))];
console.log(uniqueTags);
// ["js", "beginner", "react", "hooks", "ts", "type"]
10. ソート – sort / toSorted
10.1 sort の落とし穴 – デフォルトは文字列順
引数なしの sort() はすべて文字列として比較するため、数値ソートで意図しない結果になります。必ずコンパレータを渡します。
// ❌ 文字列比較
[10, 1, 2, 21].sort();
// → ["1", "10", "2", "21"] のように "10" < "2" になる
// ✅ 数値の昇順
[10, 1, 2, 21].sort((a, b) => a - b); // [1, 2, 10, 21]
// 数値の降順
[10, 1, 2, 21].sort((a, b) => b - a); // [21, 10, 2, 1]
// 日本語(かな・漢字)の自然な並び - localeCompare
const names = ["田中", "佐藤", "鈴木", "伊藤"];
names.sort((a, b) => a.localeCompare(b, "ja"));
console.log(names); // ["伊藤", "佐藤", "鈴木", "田中"]
10.2 オブジェクト配列の多重ソート
複数キーで並べる場合はOR 演算子で繋ぐと簡潔です。先に評価したキーが優先されます。
const users = [
{ name: "Alice", age: 30, score: 80 },
{ name: "Bob", age: 25, score: 90 },
{ name: "Carol", age: 30, score: 75 },
{ name: "Dave", age: 25, score: 90 },
];
// age 昇順 → score 降順 → name 昇順
users.sort((a, b) =>
a.age - b.age ||
b.score - a.score ||
a.name.localeCompare(b.name)
);
console.log(users);
// Bob(25,90) → Dave(25,90) → Alice(30,80) → Carol(30,75)
10.3 toSorted – Immutable ソート(ES2023)
React の state ソートなど、元配列を変えたくない場面で必須。従来は [...arr].sort() が定番でしたが、ES2023 で1 メソッドで完結するようになりました。
const original = [3, 1, 4, 1, 5, 9, 2, 6];
// 元を変えず新配列を返す
const sorted = original.toSorted((a, b) => a - b);
console.log(sorted); // [1, 1, 2, 3, 4, 5, 6, 9]
console.log(original); // [3, 1, 4, 1, 5, 9, 2, 6] ← 不変
// React での使用例
const [items, setItems] = useState([...]);
const handleSort = () => {
setItems((prev) => prev.toSorted((a, b) => a.score - b.score));
// setItems(prev.sort(...)) だと同一参照のため再レンダーしない場合あり!
};
10.4 toReversed – Immutable 反転(ES2023)
reverse() は破壊的なので、toReversed() を優先します。
const arr = [1, 2, 3, 4, 5];
// 非破壊
const reversed = arr.toReversed();
console.log(reversed); // [5, 4, 3, 2, 1]
console.log(arr); // [1, 2, 3, 4, 5] ← 不変
// 旧来のイディオム
const oldWay = [...arr].reverse(); // 同等だが冗長
11. ES2023 新メソッド – with でピンポイント書き換え
with(index, value) は「特定インデックスを書き換えた新配列」を返す Immutable な代入です。React state や Redux 更新の冗長な spread をシンプルに置き換えられます。
const arr = ["a", "b", "c", "d"];
// 旧来のイミュータブル代入
const old = [...arr.slice(0, 2), "B", ...arr.slice(3)];
// ES2023: with で一発
const next = arr.with(2, "C");
console.log(next); // ["a", "b", "C", "d"]
console.log(arr); // ["a", "b", "c", "d"] ← 不変
// 負のインデックスもサポート
arr.with(-1, "Z"); // ["a", "b", "c", "Z"]
// React state での使用例
const [list, setList] = useState([1, 2, 3]);
const updateAt = (i, v) => setList((prev) => prev.with(i, v));
12. 配列生成 – Array.from / Array.of / fromAsync
12.1 Array.from – iterable / arrayLike から配列化
Array.from はiterable(Map / Set / NodeList / 文字列など)や arrayLike({length: n})を配列に変換する万能メソッド。第 2 引数に map 関数も渡せます。
// NodeList を配列に
const divs = Array.from(document.querySelectorAll("div"));
divs.map((el) => el.textContent);
// Set を配列に(重複除去の鉄板パターン)
const unique = Array.from(new Set([1, 2, 2, 3, 3, 3]));
console.log(unique); // [1, 2, 3]
// Map を配列に
const m = new Map([["a", 1], ["b", 2]]);
Array.from(m); // [["a", 1], ["b", 2]]
Array.from(m.keys()); // ["a", "b"]
// arrayLike + map 関数 で連番生成
const range = Array.from({ length: 5 }, (_, i) => i);
console.log(range); // [0, 1, 2, 3, 4]
const squared = Array.from({ length: 5 }, (_, i) => i * i);
console.log(squared); // [0, 1, 4, 9, 16]
// 1 〜 N の range
const range1toN = (n) => Array.from({ length: n }, (_, i) => i + 1);
range1toN(5); // [1, 2, 3, 4, 5]
12.2 Array.of – リテラル配列の代替
Array(5) と Array.of(5) は挙動が全く違うので注意。Array.of は引数を要素として持つ配列を作ります。
Array(5); // [ <5 empty> ] ← 長さ 5 の空配列
Array.of(5); // [5] ← 要素 5 の配列
Array(1, 2); // [1, 2]
Array.of(1, 2); // [1, 2]
// Array コンストラクタは引数 1 つだと挙動が変わるので
// 「要素として確実に保持したい」場合は Array.of が安全
12.3 Array.fromAsync – 非同期 iterable から(ES2024)
ES2024 で追加。async iterable(ReadableStream / async generator など)を配列化できます。Promise.all(arr.map(...)) 的な用途もカバー。
// async generator
async function* asyncRange(n) {
for (let i = 0; i < n; i++) {
await new Promise((r) => setTimeout(r, 10));
yield i;
}
}
const arr = await Array.fromAsync(asyncRange(5));
console.log(arr); // [0, 1, 2, 3, 4]
// Promise の配列も解決して配列化
const ids = [1, 2, 3];
const users = await Array.fromAsync(ids, async (id) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
});
// → 各 fetch の結果配列(直列実行)
// 直列なので並列にしたい場合は Promise.all が引き続き有利
const parallel = await Promise.all(
ids.map((id) => fetch(`/api/users/${id}`).then((r) => r.json()))
);
13. グルーピング – Object.groupBy / Map.groupBy (ES2024)
ES2024 で正式追加された標準 groupBy。これまで lodash _.groupBy に依存していた処理がネイティブで書けます。
13.1 Object.groupBy – キーで分類
戻り値はプレーンオブジェクト。キーは文字列に強制変換される点に注意。
const products = [
{ name: "りんご", category: "fruit", price: 100 },
{ name: "にんじん", category: "vegetable", price: 80 },
{ name: "バナナ", category: "fruit", price: 120 },
{ name: "ピーマン", category: "vegetable", price: 60 },
];
const grouped = Object.groupBy(products, (p) => p.category);
console.log(grouped);
// {
// fruit: [{...りんご}, {...バナナ}],
// vegetable: [{...にんじん}, {...ピーマン}],
// }
// 年代別グルーピング
const users = [
{ name: "A", age: 23 }, { name: "B", age: 31 },
{ name: "C", age: 28 }, { name: "D", age: 45 },
];
const byDecade = Object.groupBy(users, (u) => `${Math.floor(u.age / 10)}0代`);
// { "20代": [A, C], "30代": [B], "40代": [D] }
13.2 Map.groupBy – オブジェクトキーで分類
キーがオブジェクトや、文字列に変換したくない場合は Map.groupBy を使います。戻り値は Map。
const orders = [
{ user: { id: 1 }, amount: 1000 },
{ user: { id: 2 }, amount: 2000 },
{ user: { id: 1 }, amount: 500 },
];
// user オブジェクトをキーに(同じ参照だけが集まる)
const u1 = { id: 1 };
const u2 = { id: 2 };
const fixed = [
{ user: u1, amount: 1000 },
{ user: u2, amount: 2000 },
{ user: u1, amount: 500 },
];
const byUser = Map.groupBy(fixed, (o) => o.user);
console.log(byUser.get(u1)); // [{user:u1, amount:1000}, {user:u1, amount:500}]
14. 実用パターン集 – chunk / unique / partition / zip
lodash や Ramda でお馴染みの定番ユーティリティを、標準メソッドだけで実装するパターン集です。
14.1 chunk – 配列を N 個ずつに分割
ページング・バルク API リクエスト・グリッド表示などで頻出。
const chunk = (arr, size) =>
Array.from({ length: Math.ceil(arr.length / size) }, (_, i) =>
arr.slice(i * size, i * size + size)
);
chunk([1, 2, 3, 4, 5, 6, 7], 3);
// [[1, 2, 3], [4, 5, 6], [7]]
// バルク API: 50 件ずつ送信
const ids = Array.from({ length: 230 }, (_, i) => i + 1);
for (const batch of chunk(ids, 50)) {
await fetch("/api/bulk", { method: "POST", body: JSON.stringify(batch) });
}
14.2 unique / dedupe – 重複除去
プリミティブなら Set、オブジェクトならキーを指定する関数が必要です。
// プリミティブの重複除去
const unique = (arr) => [...new Set(arr)];
unique([1, 2, 2, 3, 3, 3]); // [1, 2, 3]
// オブジェクト配列をキーで重複除去
const uniqueBy = (arr, keyFn) => {
const seen = new Map();
for (const item of arr) {
const k = keyFn(item);
if (!seen.has(k)) seen.set(k, item);
}
return [...seen.values()];
};
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice Updated" }, // 重複
];
uniqueBy(users, (u) => u.id);
// [{ id:1, name:"Alice" }, { id:2, name:"Bob" }]
14.3 partition – 条件で 2 つに分ける
filter を 2 回呼ぶより1 度の走査で済む partition の方が高速。reduce で実装するのが定番です。
const partition = (arr, pred) =>
arr.reduce(
([pass, fail], item) => (pred(item) ? [[...pass, item], fail] : [pass, [...fail, item]]),
[[], []]
);
const [adults, minors] = partition([15, 22, 18, 30, 17], (n) => n >= 18);
console.log(adults); // [22, 18, 30]
console.log(minors); // [15, 17]
// 高頻度更新の場合は mutable 版の方が速い
const partitionFast = (arr, pred) => {
const pass = [];
const fail = [];
for (const x of arr) (pred(x) ? pass : fail).push(x);
return [pass, fail];
};
14.4 zip / unzip – 配列同士を組み合わせる
2 つの配列をペアに組み立てる zip、その逆の unzip です。テーブル表示や API レスポンス組み立てで使えます。
const zip = (...arrs) => {
const len = Math.min(...arrs.map((a) => a.length));
return Array.from({ length: len }, (_, i) => arrs.map((a) => a[i]));
};
zip([1, 2, 3], ["a", "b", "c"]);
// [[1, "a"], [2, "b"], [3, "c"]]
zip(["name", "age", "city"], ["Alice", 28, "Tokyo"]);
// [["name","Alice"], ["age",28], ["city","Tokyo"]]
// 3 配列 zip
zip([1, 2, 3], ["a", "b", "c"], [true, false, true]);
// [[1, "a", true], [2, "b", false], [3, "c", true]]
// unzip
const unzip = (pairs) =>
pairs.reduce(
(acc, pair) => (pair.forEach((v, i) => (acc[i] ??= []).push(v)), acc),
[]
);
unzip([[1, "a"], [2, "b"], [3, "c"]]);
// [[1, 2, 3], ["a", "b", "c"]]
15. パイプライン処理 – チェイン記法
配列メソッドの真価はメソッドチェインで発揮されます。SQL の WHERE → GROUP BY → ORDER BY 的な感覚で処理を書けます。
15.1 売上集計の実例
商品データから「在庫あり」「カテゴリ別売上 TOP 3」を一気に算出するパイプラインです。
const orders = [
{ product: "本", category: "書籍", price: 1200, stock: 10 },
{ product: "ペン", category: "文具", price: 200, stock: 0 },
{ product: "雑誌", category: "書籍", price: 800, stock: 5 },
{ product: "ノート", category: "文具", price: 300, stock: 20 },
{ product: "鉛筆", category: "文具", price: 100, stock: 3 },
{ product: "辞書", category: "書籍", price: 3000, stock: 2 },
];
const result = orders
.filter((o) => o.stock > 0) // 在庫あり
.map((o) => ({ ...o, revenue: o.price * o.stock })) // 売上計算
.toSorted((a, b) => b.revenue - a.revenue) // 降順
.slice(0, 3); // TOP 3
console.log(result);
// 辞書(6000) → ノート(6000) → 本(12000)
15.2 カテゴリ別集計
Object.groupBy + reduce でカテゴリ別の合計を一発算出します。
const summary = Object.entries(
Object.groupBy(orders, (o) => o.category)
).map(([category, items]) => ({
category,
totalRevenue: items.reduce((s, o) => s + o.price * o.stock, 0),
itemCount: items.length,
avgPrice: items.reduce((s, o) => s + o.price, 0) / items.length,
})).toSorted((a, b) => b.totalRevenue - a.totalRevenue);
console.log(summary);
// [
// { category: "書籍", totalRevenue: 22000, itemCount: 3, avgPrice: 1666.6 },
// { category: "文具", totalRevenue: 6900, itemCount: 3, avgPrice: 200 },
// ]
16. パフォーマンス比較 – for / forEach / map / reduce
「for ループが一番速い」とよく言われますが、現代の V8 ではほぼ差がないのが実情。ボトルネックは大抵{ ...obj } や中間配列生成です。
16.1 ベンチマークコード
performance.now() で計測する基本パターンです。N 回ループして平均を出します。
const N = 1_000_000;
const arr = Array.from({ length: N }, (_, i) => i);
const bench = (label, fn) => {
const t = performance.now();
fn();
console.log(`${label}: ${(performance.now() - t).toFixed(2)} ms`);
};
bench("for", () => {
let sum = 0;
for (let i = 0; i < arr.length; i++) sum += arr[i];
});
bench("for..of", () => {
let sum = 0;
for (const v of arr) sum += v;
});
bench("forEach", () => {
let sum = 0;
arr.forEach((v) => (sum += v));
});
bench("reduce", () => {
arr.reduce((s, v) => s + v, 0);
});
// 結果例(Node.js 22, M1 Mac):
// for: 2.1 ms
// for..of: 6.8 ms
// forEach: 7.5 ms
// reduce: 8.2 ms
16.2 中間配列を減らす最適化
filter().map().filter() はそのたびに新配列が作られます。大量データなら reduce 1 発で書く方が高速です。
// 中間配列 2 つ生成
const slow = arr
.filter((x) => x % 2 === 0)
.map((x) => x * 2);
// 1 走査で完結
const fast = arr.reduce((acc, x) => {
if (x % 2 === 0) acc.push(x * 2);
return acc;
}, []);
// flatMap で同等のことを宣言的に
const flatVer = arr.flatMap((x) => (x % 2 === 0 ? [x * 2] : []));
17. TypeScript の型推論
17.1 map / filter の型推論
TypeScript は配列メソッドの戻り型をかなり賢く推論しますが、filter による型絞り込みは型ガード関数を書く必要があります。
// map は推論バッチリ
const nums: number[] = [1, 2, 3];
const strs = nums.map((n) => n.toString()); // string[]
// filter は戻り型を絞ってくれない
const mixed: (string | null)[] = ["a", null, "b"];
const nonNull = mixed.filter((s) => s !== null);
// 型は (string | null)[] のまま!
// 型ガード関数で正しく絞る
const onlyStr = mixed.filter((s): s is string => s !== null);
// onlyStr: string[]
// ユーティリティ化
const isDefined = <T>(v: T | null | undefined): v is T => v != null;
const safe = mixed.filter(isDefined); // string[]
17.2 reduce の初期値で型を明示
reduce は初期値の型から accumulator の型を推論するため、初期値に型注釈を付けると安全です。
// 明示しないと number[] と推論されてエラー
const arr = [1, 2, 3];
const map = arr.reduce<Record<string, number>>((acc, n) => {
acc[`key${n}`] = n * 10;
return acc;
}, {});
// { key1: 10, key2: 20, key3: 30 }
// as const で readonly タプル
const tuple = [1, "a", true] as const;
// readonly [1, "a", true] と推論される
18. Generator → 配列化
Generator は遅延評価で値を生成するイテレータです。Array.from や spread で配列化できます。
// フィボナッチ数列の Generator
function* fib(limit) {
let [a, b] = [0, 1];
while (a <= limit) {
yield a;
[a, b] = [b, a + b];
}
}
// 配列化(2 通り)
[...fib(100)]; // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Array.from(fib(100)); // 同上
// range 関数を Generator で
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) yield i;
}
[...range(0, 10, 2)]; // [0, 2, 4, 6, 8]
19. structuredClone で深いコピー
配列をネスト構造ごと完全コピーしたい場合、ES2022 の structuredClone が最も安全。JSON.parse(JSON.stringify(x)) の罠(Date 消失 / undefined 消失 / 循環参照不可)を回避できます。
const original = [
{ name: "A", data: { tags: ["x", "y"], date: new Date() } },
{ name: "B", data: { tags: ["z"], date: new Date() } },
];
// 浅いコピー(中のオブジェクトは共有される ← バグの温床)
const shallow = [...original];
shallow[0].data.tags.push("z"); // original も変わる!
// 深いコピー(完全独立)
const deep = structuredClone(original);
deep[0].data.tags.push("z"); // original は無傷
console.log(original[0].data.tags); // ["x", "y"]
// JSON 方式との違い
const obj = { d: new Date(), u: undefined, fn: () => 1 };
JSON.parse(JSON.stringify(obj)); // Date が string、undefined / fn 消失
structuredClone(obj); // Date が Date のまま / fn だけは複製不可で例外
20. よくある落とし穴とアンチパターン
20.1 sparse array – 空要素は要注意
new Array(5) や arr[10] = "x" は空要素を含む sparse array を作ります。多くのメソッドはこの空要素をスキップするため挙動が直感と異なります。
const sparse = new Array(3);
console.log(sparse); // [ <3 empty items> ]
console.log(sparse.length); // 3
console.log(sparse[0]); // undefined
// map は空要素をスキップ!
sparse.map(() => 1); // [ <3 empty items> ] ← [1, 1, 1] にならない!
// 確実に埋めたいなら fill か Array.from
new Array(3).fill(0); // [0, 0, 0]
Array.from({ length: 3 }, () => 0); // [0, 0, 0]
20.2 配列の存在判定は Array.isArray
typeof は配列を "object" としか言わないので役に立ちません。必ず Array.isArray を使います。
typeof []; // "object" ← 役立たず
typeof [] === "object"; // true ← オブジェクトと区別できない
Array.isArray([]); // true
Array.isArray({}); // false
Array.isArray("abc"); // false
Array.isArray(new Array()); // true
20.3 splice の挿入は分かりにくい
splice は引数が多く読みづらい。Immutable に書きたいなら toSpliced や spread を優先します。
// ❌ splice は意味が読み取りづらい
arr.splice(2, 0, "X"); // 「index 2 に X を挿入」と分かる人だけ分かる
// ✅ spread の方が意図が明確
[...arr.slice(0, 2), "X", ...arr.slice(2)];
// ✅ ES2023: toSpliced(非破壊)
arr.toSpliced(2, 0, "X");
21. まとめ – 配列メソッド習得チェックリスト
本記事で扱った配列メソッドの習熟度チェックリストです。実務で迷わず使い分けられているか自己評価してみてください。
- 破壊 vs 非破壊: 各メソッドが元配列を変えるかを即答できる
- map / filter / reduce: パイプライン形式で滑らかに書ける
- findLast / findLastIndex: ログ・履歴の末尾検索で使えている
- toSorted / toReversed / toSpliced / with: React state を Immutable に更新できる
- Object.groupBy / Map.groupBy: lodash 依存を外してネイティブで書ける
- Array.from: range / 連番 / Set からの変換を即書ける
- Array.fromAsync: async iterable の配列化に使える
- chunk / unique / partition / zip: 自作ユーティリティとして書ける
- structuredClone: ネスト配列の深いコピーを安全に行える
- sparse array:
new Array(n)の落とし穴を避けられる
JavaScript の配列メソッドは ES2023〜2024 で大きく進化し、Immutable・関数型スタイルがライブラリなしで完結する時代になりました。とはいえ、各メソッドの性質を把握せず雰囲気で書くと、知らぬ間に state を破壊したり、パフォーマンスが激落ちしたりします。本記事のチェックリストを定期的に振り返り、配列処理の地力を強化していきましょう。
関連記事として、JavaScript ベストプラクティス 10 選(言語全般の作法)、async/await 完全実践ガイド(非同期処理パターン)も併せて読むと、JavaScript の基礎体力が一段上のレベルへ引き上がります。

コメント