基礎

JavaScriptのスプレッド構文入門|配列・オブジェクトの展開とコピー

スプレッド構文(Spread Syntax)は、配列やオブジェクトの要素を個別に展開するES6の機能です。残余引数と同じ ... 記号を使いますが、「まとめる」のではなく「展開する」のが役割です。配列のコピー、結合、関数への引数渡し、オブジェクトのマージなど、幅広い場面で使われます。

この記事では、配列とオブジェクトそれぞれでのスプレッド構文の使い方と、実務で頻出するパターンを解説します。

基本的な使い方(配列)

配列の前に ... を付けると、配列の各要素が個別に展開されます。

JavaScript
const numbers = [1, 2, 3];

// 配列の展開
console.log(...numbers);       // 個別の値として展開
console.log([...numbers]);     // 新しい配列にコピー

// 配列のコピー
const original = [10, 20, 30];
const copy = [...original];
copy.push(40);
console.log('原本:', original);  // 変更されない
console.log('コピー:', copy);

// 配列の結合
const front = [1, 2, 3];
const back = [4, 5, 6];
const merged = [...front, ...back];
console.log('結合:', merged);

// 途中に挿入
const withInsert = [1, 2, ...back, 7, 8];
console.log('挿入:', withInsert);
実行結果
1 2 3
[1, 2, 3]
原本: [10, 20, 30]
コピー: [10, 20, 30, 40]
結合: [1, 2, 3, 4, 5, 6]
挿入: [1, 2, 4, 5, 6, 7, 8]

[...original] は配列の浅いコピー(shallow copy)を作成します。concat メソッドでも同様のことができますが、スプレッド構文の方が直感的で、途中に要素を挟む場合にも柔軟に対応できます。

関数の引数に展開

配列の要素を関数の個別の引数として渡す場合、スプレッド構文が便利です。

JavaScript
// Math.max に配列を渡す
const scores = [85, 92, 78, 95, 88];
console.log('最大値:', Math.max(...scores));
console.log('最小値:', Math.min(...scores));

// 従来の方法(apply)との比較
console.log('最大値(apply):', Math.max.apply(null, scores));

// 複数の配列をまとめて渡す
const group1 = [10, 20, 30];
const group2 = [40, 50, 60];
console.log('全体の最大:', Math.max(...group1, ...group2));
実行結果
最大値: 95
最小値: 78
最大値(apply): 95
全体の最大: 60

従来は Function.prototype.apply を使う必要がありましたが、スプレッド構文なら Math.max(...array) と書くだけで済みます。

オブジェクトのスプレッド構文

ES2018(ES9)からオブジェクトでもスプレッド構文が使えるようになりました。オブジェクトのコピーやマージに活用できます。

JavaScript
// オブジェクトのコピー
const defaults = { theme: 'light', language: 'ja', fontSize: 14 };
const copy2 = { ...defaults };
console.log(copy2);

// オブジェクトのマージ(後から指定した値が優先)
const userSettings = { theme: 'dark', fontSize: 16 };
const config = { ...defaults, ...userSettings };
console.log('マージ:', config);

// プロパティの上書きと追加
const baseUser = { name: '田中', role: '一般' };
const adminUser = { ...baseUser, role: '管理者', permissions: ['read', 'write'] };
console.log(adminUser);

// 特定のプロパティだけ変更するパターン
const state = { count: 0, loading: false, error: null };
const newState = { ...state, count: state.count + 1, loading: true };
console.log('旧:', state);
console.log('新:', newState);
実行結果
{ theme: 'light', language: 'ja', fontSize: 14 }
マージ: { theme: 'dark', language: 'ja', fontSize: 16 }
{ name: '田中', role: '管理者', permissions: ['read', 'write'] }
旧: { count: 0, loading: false, error: null }
新: { count: 1, loading: true, error: null }

オブジェクトのスプレッド構文は、Reactの状態管理(setState)やReduxのReducerで頻繁に使われます。元のオブジェクトを変更せずに新しいオブジェクトを生成するイミュータブルなパターンの基本です。

実践例

JavaScript
// 配列の重複除去
const withDuplicates = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(withDuplicates)];
console.log('重複除去:', unique);

// 文字列を1文字ずつ配列にする
const chars = [..."JavaScript"];
console.log(chars);

// 条件付きプロパティの追加
const isAdmin = true;
const user2 = {
  name: '佐藤',
  ...(isAdmin && { role: 'admin', permissions: ['all'] })
};
console.log(user2);
実行結果
重複除去: [1, 2, 3, 4]
['J', 'a', 'v', 'a', 'S', 'c', 'r', 'i', 'p', 't']
{ name: '佐藤', role: 'admin', permissions: ['all'] }
スプレッド構文 vs rest構文

同じ ... 記号ですが、使われる場所で意味が変わります。関数の引数定義や分割代入の左辺では「まとめる」(rest)、配列リテラルや関数呼び出しの中では「展開する」(spread)として働きます。

浅いコピーに注意

スプレッド構文によるコピーは浅いコピー(shallow copy)です。ネストしたオブジェクトや配列は参照が共有されるため、変更が元のデータに影響します。深いコピーが必要な場合は structuredClone(obj)(ES2022+)または JSON.parse(JSON.stringify(obj)) を使ってください。

まとめ

  • ... で配列やオブジェクトの要素を個別に展開できる
  • [...array] で配列のコピー、[...a, ...b] で結合ができる
  • Math.max(...array) のように関数の引数に展開して渡せる
  • {...obj, key: value} でオブジェクトのマージ・上書きができる
  • 浅いコピーなので、ネストした参照は共有されることに注意する