解説
スプレッドでコピーしたのに元も変わる?入れ子の参照とstructuredCloneを比べよう
オブジェクトの外側が別でも、中のオブジェクトを共有する場合がある。浅いコピーと、対応するデータの深い複製を作例で比べる。

設定の変更案を作るため、{...original}でコピーした。ところが中の名前を書き換えたら、元のデータまで変わってしまった。スプレッド構文のコピーは浅いコピーだからだよ。
今回は入れ子のある小さなオブジェクトで、何を共有しているか確認する。ブラウザだけで使える作例は、架空の名前を扱い、保存や外部送信はしない。
外側が別でも、入れ子は共有され得る
const original = { profile: { name: "青" } };
const draft = { ...original };
draft.profile.name = "緑";
console.log(original.profile.name); // "緑"
console.log(draft === original); // false
外側のoriginalとdraftは別のオブジェクト。しかしprofileが指す内側のオブジェクトは共有されている。内側のnameを変更すると、元から見た値も変わるんだ。
「変数名が違う」「外側の===がfalse」という確認だけでは、中身まで独立しているとは分からないよ。
対応するデータならstructuredCloneで複製できる
const original = { profile: { name: "青" } };
const draft = structuredClone(original);
draft.profile.name = "緑";
console.log(original.profile.name); // "青"
console.log(draft.profile.name); // "緑"
今回のような通常のデータなら、structuredCloneで入れ子を含む複製を作れる。元のprofileとコピー先のprofileは別になるよ。
ただし、あらゆるJavaScriptの値を完全に複製する関数ではない。関数やDOMノードなど、対応しないものもある。画面の状態や独自クラスの動作まで、そのまま保存できると決めつけないようにしよう。
作例では同じ初期値から比べる
- 浅いコピーを選んで「コピー先の名前を変える」を押す。元も緑になる。
- structuredCloneを選んで同じ操作をする。元は青のまま残る。
- 外側の一致、内側の一致、両方の表示を確認する。
- 「最初から試す」で未実行の状態へ戻す。
各実行は新しい初期データから始めるので、前の操作の影響を持ち越さず比較できる。未対応のブラウザではstructuredCloneの実行を止め、未対応と表示する作りだよ。
コピーを作る以外の設計も考える
一か所を変えたいだけなら、必要な階層ごとに新しいオブジェクトを作る方法もある。大きなデータ全体をいつも複製するかは、更新範囲と扱う値の種類を見て決めたいね。
今回の例は元を変えない方法を比較するためのもので、データのバックアップやサーバーへの保存を作ったものではない。
編集部ではNode.jsとChromiumで、外側と内側の参照、書き換え後の値、リセットを確認した。関数・DOM・独自クラスなどの複製まで検証した結果ではないよ。
配列のコピーも中の要素に注意が必要。sortとtoSortedとfindとfilterも合わせて読んでみよう。
動かしてみよう:浅いコピーと入れ子の複製
コピー先を書き換え、元の名前と内側の参照がどうなるか比べよう。

みんなのコメント 0
感想や、試してみたことを聞かせてね。コメント・返信は承認後に公開されます。
最初のコメントをどうぞ。