はじめに
jsでの開発をしていると、deep copyが必要になる時が多いですよね。
色々方法がある中で、どんな場面でどんな方法を使うべきかまとめてみました。
Deep Copyとは?
元オブジェクトに影響を与えずに同じ値を持つ新しいオブジェクトを生成することです。
コピー先のオブジェクトのプロパティがコピー元のオブジェクトのプロパティと同一の参照(同じ値を指す)で共有しないコピー方法のこと
対比する概念としてshallow copyがあって、shallow copyは新しいオブジェクトに変更があると元オブジェクトにも影響が出るものとなっています。
Deep Copyの方法
1. Json.parse(Json.stringify())
jsでオブジェクトはJSONの形にすることができるので、Jsonクラスのparseメソッドを利用して同じオブジェクトを生成することができます。
個人的にはdeep copyを行うにあたってどんな処理が行われているか一番わかりやすいと感じます。
2. structuredClone
ブラウザAPIとしてネイティブな実装になっているメソッドです。
3. cloneDeep
lodashというライブラリが提供するメソッドです。
実例
const original = {
name: "田中",
info: {
age: 25,
hobbies: ["読書", "旅行"]
},
date: new Date()
};
// 1. JSON.parse(JSON.stringify)
const copy2 = JSON.parse(JSON.stringify(original));
// 2. structuredClone
const copy1 = structuredClone(original);
// 3. lodash の cloneDeep
import _ from 'lodash';
const copy3 = _.cloneDeep(original);
比較
1. Json
最もシンプルで広くサポートされている方法です。
しかし、stringifyでの実装になっているため、オブジェクトによって正しく処理されない可能性があります。
- undefined(削除される)
- 関数(削除される)
- Symbol(削除される)
- 循環参照(エラー発生)
- Date(文字列に変換)
- Map/Set(空のオブジェクトに変換)
2. structuredClone
Date, Map, Set, ArrayBuffer, Blob, Fileなどのオブジェクトを正しくコピーすることができます。
循環参照(circular references)に対応していますが、関数やSymbolなどはコピーできない制限があります。
また、これは業務で経験したことですが、structuredCloneは2022年にできた機能であるためそれ以前のバージョンのブラウザでは正しく動作しません。
ほとんど起きないことではありますが、もしdeep copyで権限などの処理をしている場合は、想定外の挙動をする可能性がありますね。
3. lodash
一番柔軟で機能が豊富な方法です。
customizerという関数があってカスタマイズ可能です。他の方法に比べて違うところは、ほぼすべてのJavaScriptオブジェクトに対応しています。
循環参照にも対応していますが、外部ライブラリへの依存が必要で、パフォーマンスは3つの中で最も低いです。
循環参照とは?
私は調べているときに循環参照がよくわからなかったので一緒にまとめてみました。
循環参照とは、オブジェクトの中に直接または間接的に自分自身への参照が含まれている状態のことです。
以下のような場面がその例となります。
// 循環参照を含むオブジェクト
const family = {
name: "田中家"
};
const father = {
name: "田中太郎",
family: family // family への参照
};
family.father = father; // father への参照
// JSON.stringify で変換しようとすると...
try {
console.log(JSON.stringify(family));
} catch (error) {
console.log("エラー発生:", error.message);
// 出力: エラー発生: Converting circular structure to JSON
}