JavaScript | 配列・オブジェクト:オブジェクト操作 – 浅いコピーと深いコピー

JavaScript JavaScript
スポンサーリンク

浅いコピーと深いコピーとは何か

浅いコピーは「外側のプロパティだけ新しいオブジェクトに複製し、入れ子(オブジェクトや配列)の“参照”はそのまま共有する」コピーです。深いコピーは「入れ子の内部まで再帰的に複製し、元と完全に独立した新しいデータ構造を作る」コピーです。ここが重要です:浅いコピーは速くて軽いが“内側は同じ箱”、深いコピーは安全だが重く、型対応やコストに注意が必要です。

const orig = { user: { name: "Alice" }, tags: ["js"] };

// 浅いコピー
const shallow = { ...orig };          // または Object.assign({}, orig)
shallow.user.name = "Bob";
console.log(orig.user.name);          // "Bob"(内側は共有)

// 深いコピー(対応環境)
const deep = structuredClone(orig);
deep.user.name = "Carol";
console.log(orig.user.name);          // "Alice"(完全に独立)
JavaScript

浅いコピーの基本(スプレッド構文/Object.assign)

どんなときに浅いコピーで十分か

外側のフィールドを置き換えるだけ、または入れ子を触らない更新なら浅いコピーで十分です。UI状態の「非破壊更新(新インスタンス)」に向きます。

const state = { count: 0, prefs: { theme: "light" } };
// 外側のみ更新
const next = { ...state, count: state.count + 1 };
JavaScript

入れ子を更新したいときの“部分的な深掘り”

浅いコピーでも「更新したい階層だけさらにスプレッド」すれば、必要な部分を独立できます。

const state = { prefs: { theme: "light", lang: "ja" }, user: { name: "Alice" } };
const next = {
  ...state,
  prefs: { ...state.prefs, theme: "dark" }, // この階層だけ独立
};
JavaScript

ここが重要です:浅いコピーをベースに“更新する階層だけもう一段コピー”が、現場で最も使われる安全パターンです。


深いコピーの方法(structuredClone と代替)

structuredClone(推奨:広い型対応・循環参照対応)

structuredClone(obj) は Date、Map、Set、TypedArray、File など多くのビルトイン型に対応し、循環参照も扱えます。最も信頼できる“深いコピー”です。

const orig = new Map([["a", { x: 1 }]]);
const deep = structuredClone(orig);
deep.get("a").x = 2;
console.log(orig.get("a").x); // 1(独立)
JavaScript

JSON 経由のコピー(純粋データ限定)

JSON.parse(JSON.stringify(obj)) は数値・文字列・配列・素のオブジェクトには使えますが、関数・undefined・Symbol・Date・Map/Set・循環参照を失います。API送受信向けの“純粋データ”だけに使いましょう。

const orig = { a: 1, b: "x", c: [2, 3] };
const deep = JSON.parse(JSON.stringify(orig)); // 制限ありの深いコピー
JavaScript

手書きの再帰コピー(学習用/型対応に注意)

必要な型だけを対象にして、自作の再帰コピーを書くこともできます。現場では型の網羅と保守が難しいため、基本は structuredClone を優先します。

function deepCopy(x) {
  if (x === null || typeof x !== "object") return x;
  if (Array.isArray(x)) return x.map(deepCopy);
  const out = {};
  for (const [k, v] of Object.entries(x)) out[k] = deepCopy(v);
  return out;
}
JavaScript

型ごとの挙動と注意点(重要ポイントの深掘り)

アクセサ(get/set)とプロパティ属性

スプレッドや assign は「値をコピー」します。getter は呼ばれて値だけが移り、プロパティ属性(writable/configurable/enumerable)や getter/setter 自体は複製されません。属性まで保持したいなら Object.defineProperty で再定義が必要です。

const src = { get price() { return 100; } };
const copy = { ...src };
console.log(copy.price); // 100(値のみ)
JavaScript

クラスインスタンス・プロトタイプ

スプレッドした結果は“素のオブジェクト”になります。メソッドやプロトタイプの振る舞いは複製されません。インスタンスのまま深いコピーしたいなら structuredClone を検討します(対応外のクラスは自作の複製処理が必要)。

class User { constructor() { this.name = "A"; } greet() { return "hi"; } }
const u = new User();
const plain = { ...u }; // { name: "A" }(メソッドは含まれない)
JavaScript

配列の浅い/深い

配列のスプレッド [...] は浅いコピーです。要素がオブジェクトなら参照共有になります。要素を独立させたい場合は要素ごとにコピーするか、structuredClone を使います。

const items = [{ x: 1 }, { x: 2 }];
const shallow = [...items];
shallow[0].x = 999;
console.log(items[0].x); // 999(共有)

const safe = items.map(it => ({ ...it })); // 要素ごとに浅いコピー
JavaScript

選び方の指針(性能と安全性のバランス)

浅いコピーを基本に、必要階層だけ深掘り

  • 「外側の差し替え」だけなら浅いコピーで十分。
  • 入れ子の一部を更新したいなら、その階層でスプレッド(または assign)して独立させる。
  • 全体の独立が必要、型が複雑、循環があるなら structuredClone。

パフォーマンスを意識した分岐

深いコピーはコストが高く、巨大データに対しては影響が出ます。更新範囲が狭いなら“部分的な深掘り”で必要最小限のコピーに留めるのが実務的です。

破壊的更新を避ける(変更検知の安定化)

共有状態(UI・ストア)では“非破壊で新インスタンスを返す”ことがバグと同期問題を劇的に減らします。スプレッドはそのための第一選択です。


実践レシピ(よく使う安全な更新)

設定の合成(後勝ち)

const defaults = { theme: "light", lang: "ja", retry: 2 };
const userCfg = { retry: 5 };
const runtime = { theme: "dark" };
const cfg = { ...defaults, ...userCfg, ...runtime }; // 後勝ちで安全合成
JavaScript

フィールド除外(機密情報を落としてコピー)

const user = { id: 1, name: "Alice", password: "secret" };
const { password, ...safe } = user; // password を除外して新インスタンス
JavaScript

ネストの部分更新(UI状態)

const state = { prefs: { theme: "light", lang: "ja" }, user: { name: "A" } };
const next = {
  ...state,
  prefs: { ...state.prefs, theme: "dark" },
};
JavaScript

完全な独立が必要なとき(深いコピー)

const deep = structuredClone(state); // 全階層を独立
JavaScript

まとめ

浅いコピーは「速く・簡潔で・非破壊更新に最適」だが、入れ子は参照共有になる。深いコピーは「完全に独立」だが、コストと型対応に注意が必要。基本はスプレッドで外側を合成し、更新したい階層だけさらにスプレッドする。型が複雑・循環参照がある・完全分離が必要なら structuredClone を選ぶ。属性やアクセサは“値のみコピー”であること、クラスの振る舞いは複製されないことを理解し、目的に合わせて浅い/深いコピーを使い分ければ、初心者でも安全で読みやすいオブジェクト操作を設計できます。

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