浅いコピーと深いコピーとは何か
浅いコピーは「外側のプロパティだけ新しいオブジェクトに複製し、入れ子(オブジェクトや配列)の“参照”はそのまま共有する」コピーです。深いコピーは「入れ子の内部まで再帰的に複製し、元と完全に独立した新しいデータ構造を作る」コピーです。ここが重要です:浅いコピーは速くて軽いが“内側は同じ箱”、深いコピーは安全だが重く、型対応やコストに注意が必要です。
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(独立)
JavaScriptJSON 経由のコピー(純粋データ限定)
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 を選ぶ。属性やアクセサは“値のみコピー”であること、クラスの振る舞いは複製されないことを理解し、目的に合わせて浅い/深いコピーを使い分ければ、初心者でも安全で読みやすいオブジェクト操作を設計できます。
