Deep copy (ディープコピー)

オブジェクトの ディープコピー とは、コピー先のオブジェクトのプロパティがコピー元のオブジェクトのプロパティと同一の参照(同じ値を指す)を共有しないコピーのことです。

結果として、コピー元かコピー先のどちらかを変更しても、そのほかのオブジェクトにも変更を及ぼしていないことを保証できます。すなわち、コピー元かコピー先に意図せずに予期しない変更が加えられるこはありません。

この振る舞いはシャローコピーとは対照的で、コピー元かコピー先のどちらかを変更すると他のオブジェクトも変更される可能性があります。(なぜならば、それら2つのオブジェクトは参照を共有しているためです)

JavaScript では、オブジェクトを操作する標準の組み込み構文や関数(スプレッド構文, Array.prototype.concat(), Array.prototype.slice(), Array.from(), Object.assign(), Object.create())はディープコピーを作成しません。(代わりにシャローコピーで作成されます)。

JavaScript のオブジェクトのディープコピーを作成する一つの方法は、そのオブジェクトが シリアライズ 可能であれば JSON.stringify() でオブジェクトを JSON 文字列に変換し、 JSON.parse() で文字列から(完全に新しい) JavaScript のオブジェクトに変換することです。

let ingredients_list = ["noodles",{"list":["eggs","flour","water"]}];
let ingredients_list_deepcopy = JSON.parse(JSON.stringify(ingredients_list));

// ingredients_list_deepcopy の 'list' プロパティの値を変更します
ingredients_list_deepcopy[1].list = ["rice flour","water"]
// ingredients_list の 'list' プロパティは変更されません
console.log(ingredients_list[1].list);
// Array(3) [ "eggs", "flour", "water" ]

上記のコードの通り、ディープコピーは元のオブジェクトと参照を共有しないため、ディープコピーにいかなる変更を加えたとしても、元のオブジェクトには影響を及ぼしません。

しかしながら、上記のコードはシンプルでシリアライズ可能ですが、多くの JavaScript のオブジェクトはシリアライズ可能とは全く言えません。例えば、関数(クロージャ)、シンボルHTML DOM API においてHTML要素を表すオブジェクト、再帰データ、その他の多くのケース。これらのケースにおいて JSON.stringify() を使用したオブジェクトのシリアライズは失敗します。つまり、それらのオブジェクトのディープコピーを作成する方法はありません。

シリアライズ可能 なオブジェクトであれば、代わりに structuredClone() 関数を使用してディープコピーを作成することも可能です。structuredClone() の利点は、ただ複製するだけでなく、移譲可能オブジェクトを元のオブジェクトから新しいコピーに 転送 できることです。ただし、structuredClone() は JavaScript そのものの言語機能ではなく、代わりにブラウザや window のようなグローバルオブジェクトを実装している他の JavaScript ランタイムによる機能であるということに留意してください。また、シリアライズ不可なオブジェクトを複製するために structuredClone() を呼び出すのは JSON.stringify() が失敗するのと同様に失敗します。

関連情報