🥘

【TypeScript】Shallow copy と Deep copyの違い

に公開

はじめに

最近の状態管理ライブラリではこの問題をあまり意識しなくてよくなっているかもですが、ふとShallow copy と Deep copyの違いについて疑問を持ったのでまとめていきたいと思います。

Shallow copyとは?

Shallow copy は オブジェクトの第一階層だけをコピーする 方法です。

  • 直下のプロパティはコピーされる
  • ネストされたオブジェクトや配列は 参照が共有される
  • コピー後にネストオブジェクトの内容を変更すると、元のオブジェクトにも影響する

注意点

  • スプレッド構文や Object.assign は 自身の列挙可能なプロパティ のみコピーします。非列挙プロパティや継承プロパティはコピーされません。
  • プロトタイプチェーンや getter/setter はコピーされず、新しいオブジェクトは通常 Object.prototype を持ちます。

方法その1.オブジェクト編

  1. スプレッド構文
const obj = { a: 1, b: { c: 2 } };
const copy = { ...obj };

copy.a = 10; // → 元のobj.aには影響なし
copy.b.c = 20; // → 元のobj.b.cも20に変わる


console.log(obj.a);
console.log(obj.b.c);

出力結果
1
20

  1. Object.assign
const obj = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, obj);

copy.a = 10; // → 元のobj.aには影響なし
copy.b.c = 20; // → 元のobj.b.cも20に変わる

console.log(obj.a);
console.log(obj.b.c);

出力結果
1
20

方法その2.配列編

  1. slice
const arr: (number | number[])[] = [1, 2, [3, 4]];
const copy: (number | number[])[] = arr.slice();

console.log("初期状態:");
console.log("arr:", arr);
console.log("copy:", copy);

copy[0] = 10;
if (Array.isArray(copy[2])) {
  copy[2][0] = 99;
}

console.log("copy[0]を10、copy[2][0]を99に変更後:");
console.log("arr:", arr);
console.log("copy:", copy);

出力結果
"初期状態:"
"arr:", [1, 2, [3, 4]]
"copy:", [1, 2, [3, 4]]
"copy[0]を10、copy[2][0]を99に変更後:"
"arr:", [1, 2, [99, 4]]
"copy:", [10, 2, [99, 4]]


  1. concat
const arr: (number | number[])[] = [1, 2, [3, 4]];
const copy: (number | number[])[] = ([] as (number | number[])[]).concat(arr);

copy[1] = 20; // → 元のarr[1]には影響なし
console.log(arr)
if (Array.isArray(copy[2])) {
  copy[2][1] = 88; // → 元のarr[2][1]も88に変わる
}

console.log(arr)

出力結果
[1, 2, [3, 4]]
[1, 2, [3, 88]]


  1. Array.from
const arr: (number | number[])[] = [1, 2, [3, 4]];
const copy: (number | number[])[] = Array.from(arr);

copy[0] = 100; // → 元のarr[0]には影響なし
console.log(arr)
if (Array.isArray(copy[2])) {
  copy[2][0] = 77; // → 元のarr[2][0]も77に変わる
}
console.log(arr)

出力結果
[1, 2, [3, 4]]
[1, 2, [77, 4]]


Deep copyとは?

Deep copy(ディープコピー) は、オブジェクトや配列を「すべての階層まで完全にコピーする」方法です。そのため、ネストされたオブジェクトや配列も 別の参照 となり、コピー後に変更しても元のデータには影響しません。

  • ネストされたオブジェクト・配列もすべてコピーされる
  • コピー後にネスト部分を変更しても元のデータは変わらない
  • Shallow copy とは異なり「参照の共有」が起きない

方法

配列もオブジェクトも同じ方法で対応可能

JSON.stringify と JSON.parse を組み合わせる

もっとも手軽な方法は JSON.stringify と JSON.parse を組み合わせる方法。
ただし、注意点があります。

const original = { 
  a: 1, 
  b: { c: 2 }, 
  d: new Date(), 
  f: () => console.log("hello") 
};

const copied = JSON.parse(JSON.stringify(original));

copied.b.c = 100;

console.log(original.b.c); // 2 (影響しない)
console.log(copied.b.c);   // 100
console.log(copied.d);     // "2025-09-10T00:00:00.000Z" (Dateは文字列化)
console.log(copied.f);     // undefined (関数は消える)

structuredClone

const original = { 
  a: 1, 
  b: { c: 2 }, 
  d: new Date(), 
  r: /abc/g 
};

const copied = structuredClone(original);

copied.b.c = 100;

console.log(original.b.c); // 2
console.log(copied.b.c);   // 100
console.log(copied.d instanceof Date);  // true
console.log(copied.r instanceof RegExp); // true

Date や RegExp も正しくコピー可能。ただし、注意点もあります。

lodashのcloneDeep

import cloneDeep from "lodash/cloneDeep";

const original = { 
  a: 1, 
  b: { c: 2 }, 
  d: new Date(), 
  f: () => console.log("hello") 
};

const copied = cloneDeep(original);

copied.b.c = 100;

console.log(original.b.c); // 2
console.log(copied.b.c);   // 100
console.log(copied.d instanceof Date);  // true
console.log(typeof copied.f);           // "function" (関数も保持される)

Shallow copy と Deep copy の違い

項目 Shallow copy(浅いコピー) Deep copy(深いコピー)
コピーの範囲 第一階層のみ ネストされた階層すべて
参照の共有 ネストされたオブジェクト・配列は共有される 参照は共有されない(完全に独立)
変更の影響 ネスト部分を変更すると元のデータも変わる ネスト部分を変更しても元のデータは変わらない

Discussion