スプレッド構文{ ...obj }はネストした値までは複製しない(浅いコピー)
{ ...obj }や[...arr]は最上位のプロパティだけをコピーします。値がオブジェクトや配列の場合、コピー先とコピー元は同じ参照を共有したままです。
なぜエラーが出ないのか
出力: Osaka
(エラーなし)- JavaScriptは何も報告しません。文法として正しいためです
出力: Osaka- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるか、コードを目で追うしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 const original = { name: "Al", address: { city: "Tokyo" } }; 2 const copy = { ...original } ; ^ 3 copy.address.city = "Osaka"; 4 console.log(original.address.city);
スプレッド構文は最上位のプロパティしかコピーしません。addressオブジェクトは元と同じ参照のままなので、コピー先を変更すると元のデータも変わります。structuredClone()は深いコピーになります。
直し方: { ...original } を structuredClone(original) にします。
パターン2
1 const users = [{ name: "Al" }]; 2 const copy = [...users] ; ^ 3 copy[0].name = "Bo"; 4 console.log(users[0].name);
配列のスプレッドも要素自体(オブジェクト)はコピーせず参照をコピーします。要素の中身まで独立させたい場合はstructuredClone()を使います。
直し方: [...users] を structuredClone(users) にします。
パターン3
1 const state = { tags: ["a", "b"] }; 2 const copy = { ...state } ; ^ 3 copy.tags.push("c"); 4 console.log(state.tags.length);
tagsプロパティは配列という参照型なので、スプレッドでコピーしても同じ配列を共有したままです。コピー先へのpushが元のstateにも反映されてしまいます。
直し方: { ...state } を structuredClone(state) にします。
パターン4
1 const profile = { name: "Al", hobbies: ["chess"] }; 2 const copy = { ...profile } ; ^ 3 copy.hobbies.push("golf"); 4 console.log(profile.hobbies.length);
hobbies配列は参照型なので、スプレッドでコピーしても同じ配列を共有したままです。
直し方: { ...profile } を structuredClone(profile) にします。
パターン5
1 const cart = { items: [{ name: "apple" }] }; 2 const copy = { ...cart } ; ^ 3 copy.items[0].name = "banana"; 4 console.log(cart.items[0].name);
配列の中のオブジェクトまでは複製されません。要素自体を書き換えると元のデータにも影響します。
直し方: { ...cart } を structuredClone(cart) にします。
よくある誤解
「スプレッド構文でコピーしたから元のデータは安全」とは限りません。ネストしたオブジェクト・配列は参照がそのままコピーされるため、コピー先を変更すると元のデータも変わってしまいます。
実務での勘所
ネストしたデータまで完全に複製したい場合、以前はJSON.parse(JSON.stringify(obj))という変換を経由する裏技がよく使われていましたが、関数やundefined、Dateオブジェクトなどが正しく複製できないという制約がありました。現在はstructuredCloneというグローバル関数が標準で使えるようになっており(Node.js 17以降、主要ブラウザ)、循環参照やDate、Mapなど多くの型を正しく再帰的に複製できます。ネストしたオブジェクトを本当に独立させたい場面では、まずこの関数を検討する価値があります。