スプレッド構文による「浅いコピー」って何者?
スプレッド構文 { ...obj } は、
「既存のオブジェクトの中身を“広げて”、新しいオブジェクトを作る書き方」 です。
そしてここで超重要なのが、
このコピーは 「浅いコピー(シャローコピー)」 だということ。
一言でいうと、
表面のプロパティは新しいオブジェクトにコピーされるけど、
中に入っているオブジェクトや配列は“共有される”
というコピーです。
まずは一番シンプルな浅いコピーの例
「別のオブジェクト」がちゃんとできるパターン
const original = {
name: "太郎",
age: 25,
};
const copy = { ...original };
copy.name = "花子";
console.log(original.name); // 太郎
console.log(copy.name); // 花子
JavaScriptここで起きていることはこうです。
{ ...original } によって、name: "太郎", age: 25 というプロパティを持つ
新しいオブジェクト が作られます。
そのため、copy.name を書き換えても、original.name には影響しません。
ここまでは「理想的なコピー」に見えますよね。
このレベルなら、スプレッド構文はとても素直に動きます。
「浅いコピー」が顔を出すのはネストしたとき
ネストされたオブジェクトがある場合
本番はここからです。
const original = {
name: "太郎",
details: {
age: 25,
city: "Tokyo",
},
};
const copy = { ...original };
copy.name = "花子";
copy.details.age = 30;
console.log("original.name:", original.name); // 太郎
console.log("copy.name:", copy.name); // 花子
console.log("original.details.age:", original.details.age); // 30
console.log("copy.details.age:", copy.details.age); // 30
JavaScriptname を変えたときは、original と copy は別々なので問題なし。
でも details.age を変えたら、
元の original.details.age まで変わってしまっています。
なぜか?
{ ...original } がやっているのは、
nameプロパティの値"太郎"をコピーdetailsプロパティの値(オブジェクトへの参照)をコピー
という動きだからです。
つまり、
copy.details と original.details は
「同じオブジェクトを指している」 状態になっています。
ここが浅いコピーの本質です。
「1階層目の“枠”はコピーされるが、その中身のオブジェクトまではコピーされない」。
図でイメージしてみる
参照が“複製される”だけ
ざっくりイメージすると、こうです。
original ----> {
name: "太郎",
details: ----> { age: 25, city: "Tokyo" }
}
copy ----> {
name: "太郎",
details: ----> (↑ original.details と同じもの)
}
details という“矢印”だけがコピーされていて、
矢印の先にあるオブジェクトは共有されています。
だから、copy.details.age を変えると、
同じ矢印の先にいる original.details.age も変わるわけです。
スプレッド構文の基本的な使い方
単純なコピー
const original = { a: 1, b: 2 };
const copy = { ...original };
console.log(copy); // { a: 1, b: 2 }
JavaScriptコピーしながら一部を上書き
const original = { a: 1, b: 2 };
const copy = { ...original, b: 99 };
console.log(original); // { a: 1, b: 2 }
console.log(copy); // { a: 1, b: 99 }
JavaScript右側に書いたプロパティが優先されるので、
「元をベースにしつつ、少しだけ変えたオブジェクト」を作るのにとても便利です。
ネストを“1階層だけ”コピーしたいとき
例えば、
「details だけは新しいオブジェクトにしたい」
という場合は、こう書けます。
const original = {
name: "太郎",
details: {
age: 25,
city: "Tokyo",
},
};
const copy = {
...original,
details: {
...original.details,
},
};
copy.details.age = 30;
console.log(original.details.age); // 25
console.log(copy.details.age); // 30
JavaScriptここでは、
- 外側のオブジェクトを
{ ...original }でコピー - さらに
detailsも{ ...original.details }でコピー
という「二重の浅いコピー」をしているので、details も別物になっています。
ここが重要です。
「浅いコピー」を組み合わせることで、
必要な階層だけ“独立させる”こともできる。
代入との違いをもう一度はっきりさせる
代入は「コピー」ではなく「同じものへの別名」
const original = { name: "太郎" };
const alias = original; // これはコピーではない
alias.name = "花子";
console.log(original.name); // 花子
console.log(alias.name); // 花子
JavaScriptalias は「original と同じオブジェクトを指す別名」です。
一方、スプレッド構文はこうです。
const original = { name: "太郎" };
const copy = { ...original };
copy.name = "花子";
console.log(original.name); // 太郎
console.log(copy.name); // 花子
JavaScriptここでは、original と copy は「別のオブジェクト」です。
ここが重要です。
「{ ...obj } は“新しいオブジェクトを作る”。const b = a; は“同じオブジェクトを指す変数を増やす”。
この違いを絶対に混同しないこと。
初心者として「スプレッド構文による浅いコピー」で本当に押さえてほしいこと
大事なポイントをまとめると、こうなります。
1. { ...obj } は「新しいオブジェクト」を作る
代入とは違い、1階層目のプロパティは独立する。
2. これは“浅いコピー”である
ネストされたオブジェクトや配列は「参照がコピーされるだけ」で、共有される。
3. ネストも独立させたいなら、必要な階層ごとに { ...} を重ねる{ ...obj, nested: { ...obj.nested } } のように書く。
まずは、次のコードを自分の手で打って、
コンソールの結果をじっくり眺めてみてください。
const original = {
name: "太郎",
details: {
age: 25,
},
};
const copy = { ...original };
copy.name = "花子";
copy.details.age = 30;
console.log("original:", original);
console.log("copy:", copy);
JavaScript「どこが独立していて、どこがつながっているのか」
それを自分の目で確認することが、
“浅いコピー”という言葉をただの用語ではなく、
“体感としての理解”に変えてくれます。
