JavaScriptにおける配列の結合は、日常的な開発において避けては通れない基本的な操作の一つです。
単純に二つの配列をつなげるだけでなく、パフォーマンスへの配慮やイミュータブル(不変性)の維持、さらにはコードの可読性など、考慮すべき点は多岐にわたります。
2026年現在のモダンなフロントエンド開発およびサーバーサイド開発においては、従来の手法に加えて最新のECMAScript仕様に基づいた効率的なアプローチが定着しています。
この記事では、JavaScriptで配列を結合するための多様な手法を整理し、それぞれのメリット・デメリットや最適なユースケースについて詳しく解説していきます。
プロジェクトの要件に合わせた最適な結合方法を選択できるよう、具体的なコード例を交えながら深掘りしていきましょう。
配列結合の基本:concatメソッド
JavaScriptで古くから利用されている最も代表的な結合方法が、Array.prototype.concat()メソッドです。
このメソッドは、既存の配列に他の配列や値を結合し、新しい配列を生成して返します。
元の配列は変更されないため、非破壊的な操作を行いたい場合に非常に適しています。
複数の配列を一度に結合することも可能で、引数に複数の配列を指定するだけで簡単に実装できます。
concatメソッドの基本的な使い方
まずは、二つの配列を結合する最もシンプルな例を見てみましょう。
// 基本的なconcatの使用例
const array1 = ['apple', 'banana'];
const array2 = ['cherry', 'dragonfruit'];
const combinedArray = array1.concat(array2);
console.log(combinedArray);
console.log(array1); // 元の配列は変わらない
["apple", "banana", "cherry", "dragonfruit"]
["apple", "banana"]
このように、array1の内容は保持されたまま、新しい配列が作成されていることがわかります。
また、配列だけでなく単一の要素を引数に混ぜることも可能です。
const base = [1, 2];
const more = [3, 4];
const singleValue = 5;
const result = base.concat(more, singleValue, 6);
console.log(result);
[1, 2, 3, 4, 5, 6]
concatは非常に直感的であり、レガシーなブラウザ環境でも動作が保証されているという安心感があります。
スプレッド構文による直感的な結合
ES6(ECMAScript 2015)で導入されたスプレッド構文(...)は、現在のJavaScript開発において最も好まれる結合手法です。
配列リテラルの中でスプレッド演算子を使用することで、配列の要素を展開し、新しい配列の中に組み込むことができます。
concatと比較して、記述が簡潔であり、直感的に配列の構造を把握しやすいのが特徴です。
スプレッド構文の記述例
スプレッド構文を用いると、配列の先頭や中間、末尾など、自由な位置に要素を挿入しながら結合できます。
const colors1 = ['red', 'green'];
const colors2 = ['blue', 'yellow'];
// スプレッド構文で結合
const allColors = [...colors1, ...colors2];
console.log(allColors);
["red", "green", "blue", "yellow"]
さらに、結合の途中に別の値を差し込むことも非常に容易です。
const start = ['A', 'B'];
const end = ['E', 'F'];
const alphabet = [...start, 'C', 'D', ...end];
console.log(alphabet);
["A", "B", "C", "D", "E", "F"]
このように、コードの宣言的な性質が強まるため、メンテナンス性が向上します。
ただし、スプレッド構文もconcatと同様にシャローコピー(浅いコピー)を行うため、多次元配列を扱う際には注意が必要です。
破壊的な結合:既存の配列を書き換える方法
メモリ効率を重視する場合や、新しい配列を作成せずに既存の配列を拡張したい場合には、破壊的な手法を選択することがあります。
特に大規模なデータセットを扱う場合、新しい配列を生成し続けるとメモリ使用量が増大し、パフォーマンスに影響を及ぼす可能性があるためです。
pushメソッドとスプレッド構文の組み合わせ
Array.prototype.push()メソッドにスプレッド構文を組み合わせることで、一方の配列の要素をもう一方の配列に効率的に追加できます。
const target = [1, 2, 3];
const source = [4, 5, 6];
// target配列を直接書き換える
target.push(...source);
console.log(target);
[1, 2, 3, 4, 5, 6]
この方法は、戻り値として新しい配列を生成しないため、メモリの節約に繋がります。
しかし、関数の外部から渡された配列を破壊的に変更することは、バグの原因になりやすいため、副作用には十分に注意してください。
大規模データでの伝統的なpush.apply
スプレッド構文が普及する前は、Array.prototype.push.apply()がよく使われていました。
非常に古い環境をサポートする必要がある場合や、スプレッド構文によるスタックオーバーフローのリスクを回避したい場合に検討されます。
const arr1 = ['a', 'b'];
const arr2 = ['c', 'd'];
// 古い書き方だが、仕組みはスプレッド構文に近い
Array.prototype.push.apply(arr1, arr2);
console.log(arr1);
["a", "b", "c", "d"]
ただし、JavaScriptエンジン(V8など)の進化により、現在ではスプレッド構文でも十分に最適化されているため、基本的にはスプレッド構文を選択して問題ありません。
最新手法:ES2023以降の配列操作
JavaScriptは進化を続けており、2023年に導入された新しいメソッド群は配列の結合においても新しい選択肢を提供しています。
特にtoSplicedメソッドは、特定のインデックスに対して要素を挿入しつつ結合する操作を、非破壊的に行うことができます。
toSplicedによる非破壊的な挿入結合
従来のspliceメソッドは破壊的でしたが、toSplicedはコピーを返します。
const initial = ['Jan', 'Feb', 'May'];
const missing = ['Mar', 'Apr'];
// インデックス2の位置から0個削除し、missingの要素を挿入して新しい配列を作る
const completed = initial.toSpliced(2, 0, ...missing);
console.log(completed);
console.log(initial); // 元の配列は維持される
["Jan", "Feb", "Mar", "Apr", "May"]
["Jan", "Feb", "May"]
このメソッドの登場により、「特定の場所に別の配列を流し込む」という操作が、不変性を保ちながら一行で記述できるようになりました。
多次元配列の平坦化を伴う結合
配列の中に配列が含まれている「入れ子状」のデータを結合し、平坦な一つの配列にしたい場面も多くあります。
その際に活用するのが、Array.prototype.flat()やArray.prototype.flatMap()です。
flatメソッドでの平坦化
concatなどで結合した結果が二次元配列になってしまった場合、flatを使うことで一次元に直せます。
const nested = [[1, 2], [3, 4], [5, 6]];
const flattened = nested.flat();
console.log(flattened);
[1, 2, 3, 4, 5, 6]
結合と同時に特定の条件で要素を加工したい場合は、flatMapが非常に強力です。
mapで配列を返すと多次元になりますが、flatMapなら自動的に一段階フラットにしてくれます。
const categories = ['A', 'B'];
const subItems = (cat) => [cat + '1', cat + '2'];
// mapだと [['A1', 'A2'], ['B1', 'B2']] になる
const result = categories.flatMap(cat => subItems(cat));
console.log(result);
["A1", "A2", "B1", "B2"]
パフォーマンス比較と手法の選び方
配列の結合において、どの手法を選ぶべきかは、扱うデータのサイズと頻度によって決まります。
以下の表に、主要な手法の特性をまとめました。
| 手法 | 破壊性 | 主なメリット | 注意点 |
|---|---|---|---|
| concat | 非破壊 | 標準的、互換性が高い | 特になし |
| スプレッド構文 | 非破壊 | 直感的、柔軟な挿入が可能 | 非常に巨大な配列では引数制限の可能性 |
| push(…arr) | 破壊的 | メモリ効率が良い | 元の配列が変わる副作用 |
| forループ + push | 破壊的 | 超大規模データに最適 | コードが冗長になる |
一般的なアプリケーション開発であれば、スプレッド構文が最も推奨されます。
Reactなどのモダンなライブラリを使用している場合、状態(State)の不変性を維持することが必須であるため、concatやスプレッド構文による非破壊的な結合が標準となります。
一方で、数十万件以上の要素を持つ配列をループ内で頻繁に結合する場合、新しい配列の生成がガベージコレクションの負荷を高めることがあります。
そのような極限のパフォーマンスが求められるシーンでは、既存の配列を再利用するpushベースの手法を検討してください。
特殊な結合ケース:重複を排除する
単に結合するだけでなく、結合した結果から重複した要素を取り除きたいという要望は非常に多いです。
その場合、ES6で登場したSetオブジェクトを活用するのが最も効率的でモダンな方法です。
const listA = [1, 2, 3];
const listB = [3, 4, 5];
// 結合してからSetに渡し、再び配列に戻す
const uniqueCombined = [...new Set([...listA, ...listB])];
console.log(uniqueCombined);
[1, 2, 3, 4, 5]
この一行で、「結合」と「重複排除」を同時に、かつ高速に行うことができます。
プリミティブな値(数値や文字列)であればこの方法が最適ですが、オブジェクトの配列の場合は参照先が異なるため、filterやMapを用いた高度なマージ処理が必要になります。
オブジェクト配列の高度なマージ
実務では、単なる値の羅列ではなく、IDを持つオブジェクトの配列を結合することが頻繁にあります。
例えば、古いユーザーリストに新しいデータをマージし、IDが重複している場合は新しい方のデータで上書きするようなケースです。
const oldUsers = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
const newUsers = [
{ id: 2, name: 'Robert' }, // ID 2を更新
{ id: 3, name: 'Charlie' }
];
// Mapを使って一意性を担保しながら結合
const userMap = new Map();
[...oldUsers, ...newUsers].forEach(user => userMap.set(user.id, user));
const mergedUsers = Array.from(userMap.values());
console.log(mergedUsers);
[
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Robert" },
{ "id": 3, "name": "Charlie" }
]
このように、データの性質に応じてMapなどのデータ構造を併用することで、ロジックの透明性と実行速度を両立させることができます。
まとめ
JavaScriptにおける配列の結合は、単純に見えて奥が深いテーマです。
基本的にはスプレッド構文を使用することで、可読性の高い洗練されたコードを記述することができます。
プロジェクトの要件として、不変性の維持(イミュータビリティ)が求められる場合は、concatやスプレッド構文、あるいは最新のtoSplicedを活用してください。
対照的に、メモリ効率が最優先される大規模データの処理においては、破壊的なpushの活用が正解となることもあります。
それぞれのメソッドが持つ内部的な挙動を理解し、2026年の標準的なベストプラクティスに則った手法を選択することが、高品質なプログラムへの第一歩となります。
今回紹介した各種手法を使いこなし、状況に応じた最適な配列操作を実装していきましょう。
