DESIGN / CODE / INFRA / AI

解説

スプレッドでコピーしたのに元も変わる?入れ子の参照とstructuredCloneを比べよう

オブジェクトの外側が別でも、中のオブジェクトを共有する場合がある。浅いコピーと、対応するデータの深い複製を作例で比べる。

スプレッドでコピーしたのに元も変わる?入れ子の参照とstructuredCloneを比べよう
保存リストを見る →
この記事の道しるべ 4項目

設定の変更案を作るため、{...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ノードなど、対応しないものもある。画面の状態や独自クラスの動作まで、そのまま保存できると決めつけないようにしよう。

作例では同じ初期値から比べる

  1. 浅いコピーを選んで「コピー先の名前を変える」を押す。元も緑になる。
  2. structuredCloneを選んで同じ操作をする。元は青のまま残る。
  3. 外側の一致、内側の一致、両方の表示を確認する。
  4. 「最初から試す」で未実行の状態へ戻す。

各実行は新しい初期データから始めるので、前の操作の影響を持ち越さず比較できる。未対応のブラウザではstructuredCloneの実行を止め、未対応と表示する作りだよ。

コピーを作る以外の設計も考える

一か所を変えたいだけなら、必要な階層ごとに新しいオブジェクトを作る方法もある。大きなデータ全体をいつも複製するかは、更新範囲と扱う値の種類を見て決めたいね。

今回の例は元を変えない方法を比較するためのもので、データのバックアップやサーバーへの保存を作ったものではない。

編集部ではNode.jsとChromiumで、外側と内側の参照、書き換え後の値、リセットを確認した。関数・DOM・独自クラスなどの複製まで検証した結果ではないよ。

配列のコピーも中の要素に注意が必要。sortとtoSortedとfindとfilterも合わせて読んでみよう。

動かしてみよう:浅いコピーと入れ子の複製

コピー先を書き換え、元の名前と内側の参照がどうなるか比べよう。

参考・出典

公開と変更の記録
  • :初回公開

日時は日本時間です。記載のある変更は、確認できた編集記録に基づきます。更新日の違いだけから変更内容を推測して載せることはありません。

更新・訂正の方針 →

みんなのコメント 0

感想や、試してみたことを聞かせてね。コメント・返信は承認後に公開されます。

最初のコメントをどうぞ。