JSでのディープコピー、シャローコピー
2022/02/05
そろそろディープコピーmethodがモダンブラウザで実装されるかも?
JSのディープコピーが可能なstructuredClone
methodは以前からFirefoxのみで使用可能でしたが、近々その他のモダンブラウザでも実装されるかもしれません。
(SafariのPreview版ではすでに実装済)
このstructuredClone
を使用しれば、今まではlodash
やJSON.parse
を使って行っていたディープコピーがよりシンプルに行えるようになります。
const obj = {
a: 'original',
b: {
c: 'original'
},
}
// deep copy
const copy = structuredClone(obj)
copy.a = 'change'
copy.b.c = 'change'
console.log(obj.b.c) // -> 'original'
console.log(copy.b.c) // -> 'change'
そもそもディープコピー、シャローコピーとは?
ここから先はディープコピー、シャローコピーがよくわかってない人向けの説明です。
obj.a
、obj.b.c
はそれぞれどうなる?
ディープコピー、シャローコピーを理解するために以下のobj.a
、obj.b.c
がそれぞれどのように変化するか考えてみます。
const obj = {
a: 'original',
b: {
c: 'original'
},
}
const copy = Object.assign({}, obj)
// もしくはconst copy = { ...obj }とか
copy.a = 'change'
copy.b.c = 'change'
console.log(obj.a) // -> ?
console.log(obj.b.c) // -> ?
答えは以下のとおりです。
console.log(obj.a) // -> 'original'
console.log(obj.b.c) // -> 'change'
値を変更したのはobj.b.c
にも関わらず、コピー元のデータも一緒に書き換わってしまいました。
これはシャローコピーでcopy
オブジェクトを生成したことが原因です。
シャローコピー
JSでの
const copy1 = Object.assign({}, obj)
const copy2 = { ...obj }
のようなオブジェクトのコピー方法はシャローコピーになります。
シャローコピーではコピー元のプロパティがprimitive型(stringやboolean)であれば、そのまま値がコピーされますが、object型であれば、その参照アドレスがコピーされることになります。
したがって、先の例でのobj.b
とcopy.b
は同じアドレスにあるプロパティを参照していることになるので、「copy.b
を変更するとobj.b
も変更される」という現象が起きるわけです(逆も然り)。
ディープコピー
コピー元とコピー先で同じ参照先のプロパティを共有してもらっては困ることも多いので、その場合はディープコピーを使用します。
ディープコピーにはいくつか方法がありますが、メジャーなところだと以下の2つになると思います。
const copy1 = JSON.parse(JSON.stringify(obj)) // 一度,文字列に変換して再度オブジェクトとして構築
const copy2 = _.cloneDeep(obj) // lodashのmethod
これらの方法はコピー元のプロパティがobject型だったとしてもコピー先で別のobjectとしてプロパティが生成されるので、シャローコピーで起こっていたようなプロパティの共有は発生しません。
シャローコピーやディープコピーの特性を知らずに実装しているとバグの温床になりかねないので、注意しましょう。
おわり
ちなみに自分だったら、obj
のコピーは↓みたいに書きます。
参考までに
const obj = {
a: 'original',
b: {
c: 'original'
},
}
// 「cが変わる」=「bを新しくする」と考え、
const newB = { c: 'change' }
const newObj = {
...obj,
b: newB // オブジェクトごと変更してあげるのもあり
}