結論
これからのオブジェクトのディープコピーにはstructuredClone
関数を使おう。
const obj = { hoge: [new Date(2023, 3, 12), 1], fuga: 'foo' };
const objClone = structuredClone(obj); // ディープコピー
obj.hoge[1] = 10;
console.log(objClone.hoge[1]); // 1
objClone.hoge[0].setFullYear(2050);
console.log(obj.hoge[0].getFullYear()); // 2023
背景
JavaScriptのオブジェクトや配列の代入はシャローコピーです。
const arr = [0, 1, 2];
const arr2 = arr; // シャローコピー
arr[0] = 10;
console.log(arr2[0]); // 10
arr2
はarr
のシャローコピーであり実際には同じ配列を指しているので、arr
に対する変更はarr2
からも反映されて見えることになります。
上の例をディープコピー、つまりarr
に対する変更でarr2
が変化しないようにするにはconst arr2 = arr.slice();
とでもすれば良いです。
それなら別に大変では無いですが、しかしそれは上の例が単純なプリミティブ値の配列だったからです。
オブジェクトや配列が多段にネストされた値全体をディープコピーするのは大変です。
const arr = [{ hoge: [0] }, { hoge: [1] }, { hoge: [2] }];
const arr2 = arr.map(x => ({ hoge: x.hoge.slice() })); // ディープコピー
arr[0].hoge[0] = 10;
console.log(arr2[0].hoge[0]); // 0
そして何より上記のやり方だと値の構造に合わせてディープコピーのコードを変更しなければなりません。
そこでプレーンオブジェクトと配列とプリミティブ値の組み合わせからなる値をディープコピーする汎用的な方法としてJSON化をする方法があります。
従来の方法
これまでオブジェクトや配列全体をディープコピーするには一旦JSON化する方法がよく使われていました。
const objClone = JSON.parse(JSON.stringify(obj));
しかしこの方法には以下のデメリットがありました。
- 数値、文字列、配列、プレーンなオブジェクト以外の値はクローンできない
-
Date
型やMap
型等はクローンできない
-
- 処理時間的にも使用メモリ量的にも非効率
- 再帰的な構造があると使えない
structuredClone
structuredClone
関数はディープコピーを行うために作られた専用関数です。これを使えば従来のJSON化を使った方法のデメリットが解消できます。
JSON化を使った方法と比べた利点は以下です。
- より多くの型がクローン可能
- 処理時間的にも使用メモリ量的にも効率的
- 再起的な構造があっても動作する
structuredClone
関数は主要なブラウザ全てで利用可能です。
使い方は結論のサンプルコードを参照。