0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptのオブジェクトコピー:shallowCopyとDeepCopyの違い

Last updated at Posted at 2025-02-17

はじめに

JavaScriptでオブジェクトを扱う際、そのコピー方法は重要な考慮事項です。
特に、ネストされたオブジェクト構造を持つ場合、適切なコピー方法を選択することが重要です。
この記事では、シャローコピーとディープコピー(特にJSONを使用した方法)の違いについて詳しく説明します。

シャローコピーの仕様

  • シャローコピー(shallowCopy) = 浅いコピー
    シャローコピーは、オブジェクトの最上位のプロパティのみをコピーします。
    ネストされたオブジェクトについては、その参照のみがコピーされ、実際のデータはコピーされません。

例えば:

const original = {
  a: 1,
  b: {
    c: 2  // <= bの持つ値がネストされたオブジェクト構造になっている
  }
};
const shallowCopy = { ...original }; // スプレッド構文でオブジェクトを展開しコピーする

shallowCopy.b.c = 3; // originalを複製した`shallowCopy`オブジェクトのcを書き換えてみる
console.log(original.b.c); // 3 (元のオブジェクトまで変更されてしまう)

この例では、shallowCopy.b.cを変更すると、original.b.cも変更されてしまいます。
つまり破壊的変更になってしまっている。
これは、bオブジェクトの参照がコピー元コピー先の間で共有されているためです。

共有されたオブジェクト:

  • オブジェクト値は、コピー元(親)とコピー先(子)の間で共有されます
  • 両方のオブジェクトが同じメモリ上のオブジェクトを指すことになります
シャローコピーでは、ネストされたオブジェクトの値は参照として複製される
// コピー元のオブジェクト
const original = {
  a: 1,
  b: {
    c: 2    -- // このオブジェクトへの参照が共有される
  }           
};            
              
// コピー先のオブジェクト
const shallowCopy = {
  a: 1,
  b: {         // 同じオブジェクトへの参照
    c: 2    --
  }
};

// 共有されるオブジェクト(相互に影響)
{
  c: 2
}
メモリ上の表現
アドレス    内容
--------   ------------------
0x001      { b: 0x002 }  // 元のオブジェクト
0x002      { c: 2 }      // ネストされたオブジェクト

0x003      { b: 0x002 }  // シャローコピーされたオブジェクト

JSONを使用したディープコピー

  • ディープコピー(Deep Copy)= 深いコピー
    ディープコピーでは、オブジェクトのすべての階層が新しいメモリ領域にコピーされます。そのため、コピー後のオブジェクトは完全に独立したものとなります。

JSONを利用したディープコピーの例

const original = {
  a: 1,
  b: {
    c: 2  // <= bの持つ値がネストされたオブジェクト構造になっている
  }
};

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

deepCopy.b.c = 3;
console.log(original.b.c); // 2 (元の`original`オブジェクトは変更されない)

この方法では、JSON.stringify() によりオブジェクトが文字列化され、JSON.parse() により新しいオブジェクトとして再構築されるため、参照の共有が発生しません。

JSONを利用したディープコピーの仕組み

  1. オブジェクトを文字列化する(JSON.stringify())
    • オブジェクト全体をJSON形式の文字列に変換し、メモリ上でフラットなデータにする
  2. 新しいオブジェクトとして再構築する(JSON.parse())
    • 文字列データを元に、新しいメモリ領域を確保してオブジェクトを作成する
  3. 元のオブジェクトとの参照関係が完全に切断される
    • ネストされたプロパティも新しいオブジェクトとして作成されるため、元のオブジェクトを変更してもコピー側には影響しない

主な違い

シャローコピーとディープコピーの比較

項目 shallowCopy JSONを使用したDeepCopy
データの独立性 ネストされたオブジェクトの参照が共有される オブジェクトのすべての階層が新しいメモリ領域で独立
メモリ使用量 ネストされたオブジェクトに新しいメモリは割り当てられない(参照の共有) オブジェクトのすべてのデータが新しいメモリ領域にコピーされる
パフォーマンス 高速 JSON変換によるオーバーヘッドがある

JSON方式の注意点

JSONを使用したディープコピーには、いくつかの制約があります。

制約

  • 関数、undefined、Symbol はコピーされない
const obj = { 
  a: 1, 
  b: undefined, 
  c: function() { console.log('Hello'); } 
};
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy); // { a: 1 } (bとcは消える)
  • Date オブジェクトは文字列に変換される
const obj = { date: new Date() };
const copy = JSON.parse(JSON.stringify(obj));
console.log(copy.date); // "2025-02-16T12:00:00.000Z"
  • 循環参照を持つオブジェクトはエラーになる
const obj = {};
obj.self = obj;
JSON.parse(JSON.stringify(obj)); // TypeError: Converting circular structure to JSON

解決策

JSONの制限を回避するため、以下の方法があります。

  • structuredClone()(ネイティブAPI, 推奨)
const copy = structuredClone(original);

  • Lodash の cloneDeep()
const _ = require('lodash');
const deepCopy = _.cloneDeep(original);


まとめ

シャローコピーとディープコピーは、それぞれ異なる用途に適しています。

  • シャローコピー
    • 高速
    • メモリ効率が良い
    • ネストされたオブジェクトは共有される
    • スプレッド構文 (...)で簡単に実装可能
  • JSONを使用したディープコピー
    • すべてのプロパティが新しいメモリ領域にコピーされる
    • 元のオブジェクトとの参照関係が切断される
    • 関数や undefined などが失われる
    • JSON.stringify() + JSON.parse() によるオーバーヘッドが発生

最適なコピー方法を選択するためには、コピーするオブジェクトの構造や用途、パフォーマンス要件を考慮することが重要です。

例えば、小規模なオブジェクトでは JSON を使った方法が手軽ですが、関数や Date を含むオブジェクトをコピーする場合はうまくいかないので structuredClone() や _.cloneDeep() の利用を検討しましょう。


参考

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?