JavaScript | ES6+ 文法:スプレッド構文 – 配列コピー

JavaScript
スポンサーリンク

スプレッド構文で「配列コピー」とは何か

スプレッド構文を使うと、[...array] のように書いて「配列の中身だけを取り出して、新しい配列を作る」ことができます。ここが重要です:const copy = arr; は“同じ配列を指す別名”ですが、const copy = [...arr]; は“中身が同じ別の配列”になります。元の配列を壊さずに扱いたいときは、必ずスプレッドでコピーを作る習慣を持つと安全です。

const arr = [1, 2, 3];

const alias = arr;     // 同じ配列を指す
const copy  = [...arr]; // 中身をコピーした新しい配列

alias[0] = 99;

console.log(arr);   // [99, 2, 3]  ← alias と同じもの
console.log(copy);  // [1, 2, 3]   ← コピーは影響を受けない
JavaScript

代入とスプレッドコピーの違いをしっかり理解する

代入は「同じ箱への別ラベル」

const original = [10, 20, 30];
const alias = original;

alias.push(40);

console.log(original); // [10, 20, 30, 40]
console.log(alias);    // [10, 20, 30, 40]
console.log(original === alias); // true(同じ配列)
JavaScript

alias に対する変更が original にも反映されてしまいます。ここが重要です:バグの多くは「同じ配列を別の変数名で触っている」ことに気づいていないことから起きます。

スプレッドコピーは「中身を複製して別の箱を作る」

const original = [10, 20, 30];
const copy = [...original];

copy.push(40);

console.log(original); // [10, 20, 30]
console.log(copy);     // [10, 20, 30, 40]
console.log(original === copy); // false(別の配列)
JavaScript

ここが重要です:「元を壊さずに加工したいとき」は、必ず [...] でコピーを作る。これだけでも、後から「なんで元のデータが変わってるの?」という事故をかなり防げます。

浅いコピーという性質(中のオブジェクトは共有される)

一段目だけコピーされるイメージ

スプレッド構文のコピーは「配列という“箱”だけ」新しくして、中身のオブジェクトはそのまま参照を共有します。ここが重要です:数値や文字列だけの配列ならほぼ気にしなくていいが、オブジェクトの配列では“共有されている”ことを意識する必要がある

const users = [
  { name: "Alice" },
  { name: "Bob" }
];

const copy = [...users];

copy[0].name = "Charlie";

console.log(users[0].name); // "Charlie"
console.log(copy[0].name);  // "Charlie"
console.log(users === copy); // false(配列は別)
console.log(users[0] === copy[0]); // true(中身のオブジェクトは同じ)
JavaScript

深くまで完全に別物にしたい「ディープコピー」が必要になる場面もありますが、最初の段階では

  • スプレッドコピーは“浅いコピー”
  • 中のオブジェクトまでコピーしたいときは別の処理が必要

とだけ把握しておけば十分です。

「元を変えない」ための基本パターン(コピー+操作)

先頭・末尾に要素を追加する

pushunshift は元配列を書き換えます。ここが重要です:安全に書きたいときは「コピーしてから足す」

const arr = [2, 3];

// 先頭に 1 を追加した新しい配列
const withHead = [1, ...arr];

// 末尾に 4 を追加した新しい配列
const withTail = [...arr, 4];

console.log(arr);      // [2, 3] (元はそのまま)
console.log(withHead); // [1, 2, 3]
console.log(withTail); // [2, 3, 4]
JavaScript

一部を変更した「新バージョン」を作る

例えば、「あるインデックスの要素だけ書き換えた配列」を作るときもスプレッドコピーを活かせます。

const original = [10, 20, 30];

// 1番目(インデックス0)だけ 99 にした新配列
const updated = [99, ...original.slice(1)];

console.log(original); // [10, 20, 30]
console.log(updated);  // [99, 20, 30]
JavaScript

ここが重要です:“不変(immutable)なスタイル”にしたいときは、コピー+新しい要素で配列リテラルを組む。React の state 更新など、実務でよく使うスタイルです。

他のコピー方法との比較(slice との違い)

slice() とスプレッドはほぼ同じ働きをする

ES5 時代から、配列の浅いコピーには arr.slice() もよく使われていました。

const arr = [1, 2, 3];

const copy1 = arr.slice(); // ES5 のコピー
const copy2 = [...arr];    // ES6 のコピー

console.log(copy1); // [1, 2, 3]
console.log(copy2); // [1, 2, 3]
JavaScript

機能としてはほぼ同じですが、

  • [...] のほうが「中身を展開している」というイメージが直感的
  • [...] は、他のスプレッド応用(結合・引数展開)と書き方が統一されている

ので、ES6 以降はスプレッド構文で統一しておくと頭の切り替えが楽になります。

代入 vs slice vs スプレッドをまとめると

const original = [1, 2, 3];

// 参照コピー(同じ配列)
const alias = original;

// 浅いコピー(別の配列)
const copyA = original.slice(); // ES5
const copyB = [...original];    // ES6+

alias[0] = 99;

console.log(original); // [99, 2, 3]
console.log(copyA);    // [1, 2, 3]
console.log(copyB);    // [1, 2, 3]
JavaScript

関数内での「安全な配列操作」パターン

引数として受けた配列をそのまま書き換えない

外から渡された配列を関数内で push などしてしまうと、呼び出し側が想定していない変更になりがちです。ここが重要です:関数内では、まずスプレッドでコピーを作ってから操作するのが安全な設計です。

function addItem(items, item) {
  // 引数の items は触らず、新しい配列を返す
  return [...items, item];
}

const original = [1, 2, 3];
const extended = addItem(original, 4);

console.log(original); // [1, 2, 3]
console.log(extended); // [1, 2, 3, 4]
JavaScript

sort / reverse のような「破壊的メソッド」と組み合わせる

sort()reverse() は配列そのものを書き換えます。元を壊したくないときは、先にコピーしてから使います。

const scores = [72, 88, 95, 64];

// 元を壊さない昇順ソート
const sorted = [...scores].sort((a, b) => a - b);

console.log(scores); // [72, 88, 95, 64]
console.log(sorted); // [64, 72, 88, 95]
JavaScript

ここが重要です:「破壊的メソッドを安全に使う」=「スプレッドでコピーしてから使う」。これも定番パターンです。

例題で理解を固める

// 1) 複数の配列を結合しつつコピー(元はそのまま)
const a = [1, 2];
const b = [3, 4];
const merged = [...a, ...b]; // [1, 2, 3, 4]
console.log(a, b);     // [1, 2] [3, 4]
console.log(merged);   // [1, 2, 3, 4]

// 2) フィルタ済みの結果をコピーして扱う
const nums = [1, 2, 3, 4, 5];
const even = nums.filter(n => n % 2 === 0);
const evenCopy = [...even]; // あとで安全に加工したいとき
console.log(evenCopy); // [2, 4]

// 3) Queue 風操作:先頭を取り除いた新配列
const queue = [10, 20, 30];
const [, ...rest] = queue; // 先頭をスキップして残りをコピー
console.log(queue); // [10, 20, 30]
console.log(rest);  // [20, 30]

// 4) 中央を差し替えた新配列
const original = [1, 2, 3, 4];
// インデックス2の値(3)だけ 99 に差し替え
const updated = [
  ...original.slice(0, 2),
  99,
  ...original.slice(3)
];
console.log(original); // [1, 2, 3, 4]
console.log(updated);  // [1, 2, 99, 4]

// 5) ネストした配列をコピーして並び替え(浅いコピーに注意)
const list = [{ id: 1 }, { id: 2 }, { id: 3 }];
const copy = [...list].reverse();
console.log(list.map(x => x.id)); // [1, 2, 3]
console.log(copy.map(x => x.id)); // [3, 2, 1]
JavaScript

まとめ

配列コピーにおけるスプレッド構文の核心は、「[...arr] で元と同じ中身を持つ“別の配列”を作り、元を壊さずに安全に操作する」ことです。代入は“同じ配列への別名”、スプレッドは“浅いコピーの新配列”。オブジェクトを含む配列では中身が共有される点に注意しつつ、「コピー+操作」のパターンを徹底することで、初心者でもバグの少ない ES6+ の配列処理を書けるようになります。

タイトルとURLをコピーしました