##はじめに
スプレッド構文についての投稿記事を作成していた時に、シャローコピー(浅い複製)、ディープコピー(深い複製)という聞きなれないワードが出てきて調べてみたので、シャローコピー、ディープコピーについて解説したいと思います。
##解説しないこと
- JavaScriptにおける参照について
- メモリについて
##シャローコピー、ディープコピーとは?
シャローコピー
、ディープコピー
とは、複製を作成時にコピー元を参照する深さの度合いによって変わる呼び名のことです。
JavaScriptは、プリミティブ以外はオブジェクトを単純代入
で変数を作成した場合、参照先は、全て元のオブジェクトを指しています。
シャローコピーは、複製した物自体は複製元を参照しているが、別の入れ物として扱われており、複製元を一部参照する複製方法。
ディープコピーは、複製元を参照しない完全に独立した複製方法。
ここで強く言っておきたいのが、単純代入とシャローコピーとディープコピーは全て別の挙動をするということです。
たくさんの記事を見ましたが、7割くらいが単純代入とシャローコピーの挙動を一緒にしており単純代入のこともシャローコピーと書いてある記事をたくさん見ましたが、代入方法は3種類でそれぞれ挙動が違います。
*シャローコピーがなぜ存在するのか?どういった意図で使用するのか?というはかなり調べましたがたどり着けず今回は記載できませんでした。 申し訳ないです。。。 引き続き調査はしていきますので、分かり次第追記いたします。
##単純代入
複製元のメモリを、複製の全てのオブジェクトが参照する代入方法
####オブジェクト
const obj1 = { a: 1, b: 2 }
const obj2 = obj1
console.log(obj1 === obj2) // true
obj2.a = 5
/* obj2はobj1を全て参照している為 1段目の変更でもobj1.aは変更されている */
console.log(obj1.a) // 5
####配列
const array = [1,2,3,4,5]
array2 = array
console.log(array === array2)
array[0] = 6
console.log(array2) // Array [6, 2, 3, 4, 5]
##シャローコピー
複製元の一部を参照する代入方法
階層の一段目は別のオブジェクトとして扱われているが二段目以降は複製元を参照している。
####オブジェクト
// オブジェクト
const obj = { a: 0, b: { c: 'c' }};
const shallowObj1 = Object.assign({}, obj);
/* 別のオブジェクトとして扱われる */
console.log(obj === shallowObj1) // false
/* 1段目は、コピーを変更してもコピー元は変わらない */
shallowObj1.a = 1
console.log(obj.a) // 0
/* 1段目以降の変更はコピー元も変わってしまう */
shallowObj1.b.c = 'a'
console.log(obj.b.c) // "a"
/* スプレッド構文で代入してもシャローコピーになる */
const shallowObj2 = {...shallowObj1}
console.log(obj === shallowObj2) // false
####配列
// 配列
const array = [
{a:1}
{a:2}
{a:3}
{a:4}
]
const sharrowArray = array.map(item => item)
/* 別の配列として扱われている */
console.log(array === sharrowArray) // false
/* 配列の中のオブジェクトのプロパティを変更するとコピー元も変更されてしまう */
sharrowArray[0].a = 2
console.log(array[0].a) // 2
// 以下も同じシャローコピーの挙動になる
const copy1 = Array.slice(0);
const copy2 = Array.concat();
const copy3 = Array.filter(function () {
return true;
});
const copy4 = Array.from(array);
##ディープコピー
複製元とは完全に別の独立した代入方法
####オブジェクト
const obj1 = {
a: 1,
b: { c: 2},
}
/* 一度,文字列に変換して再度オブジェクトとして構築 */
const deepCopy = JSON.parse(JSON.stringify(obj1))
console.log(obj1 === deepCopy) // false
deepCopy.b.c = 3
console.log(obj2) // Object { a: 1, b: Object { c: 2 } }
/* 但し、undefinedや関数が入ると削除される */
const obj2 = {
a: 1,
b: { c: 2},
d: undefined
}
/* JSON.stringifyメソッドがundefinedを変換出来ない為,プロパティdが削除されてしまう */
const hasUndefined = JSON.parse(JSON.stringify(obj2))
console.log(hasUndefined) // Object { a: 1, b: Object { c: 2 } }
####配列
const array1 = [
{ a: 1, b: 1, c: undefined},
{ a: 1, b: 1, c: undefined},
{ a: 1, b: 1, c: undefined},
]
const deepCopy = JSON.parse(JSON.stringify(array1))
console.log(array1 === deepCopy) // false
deepCopy[0].a = 2
console.log(array1[0].a) // 1
/* オブジェクト同様にundefinedが消えてしまう */
console.log(deepCopy) //Array [Object { a: 1, b: 1 }, Object { a: 1, b: 1 }, Object { a: 1, b: 1 }]
/* シャローコピーでもArray.prototype.map()を使ったが、配列の中の,オブジェクトの[構造]を返すとディ-プコピーになる */
const arrayMap = array1.map(item => ({ a: item.a, b: item.b, c: item.c }))
console.log(array1 === arrayMap) // false
console.log(arrayMap) // Array [Object { a: 1, b: 1, c: undefined }, Object { a: 1, b: 1, c: undefined }, Object { a: 1, b: 1, c: undefined }]
arrayMap[0].a = 2
console.log(array[0].a) // 1
##ライブラリを使う
import _ from 'lodash'
const obj1 = {
a: 1,
b: { c: 2},
}
/* cloneDeepメソッドを使うとディープコピーで代入される */
const obj2 = _.cloneDeep(obj1)
##おまけ
スプレッド構文を使って、ディープコピーになるように工夫してみたが、あまり綺麗では無いので参考程度にしてください。。。
const obj1 = {
a: 1,
b: { c: 2},
}
const obj2 = {
...obj1,
b: {
...obj1.b
}
}
obj2.b.c = 4
console.log(obj1.b.c) // 2
console.log(obj1 === obj2) // false
##まとめ
ディープコピーに関しては、最適解というものが無さそうでした。
個人的にはmap( )を使って構造自体を改めて返すという方法が制約も少なく使い勝手も良さそうだと感じてますが、とりあえずmap( )を使うのではなく、その時によって手法を選ぶというのが最適解な気がします。
以上、最後まで読んで頂きありがとうございました。
もし、間違っている箇所あればご指導、ご指摘よろしくお願いします。
##参考サイト