JavaScriptでディープコピーしたい時
はじめに
モーダルを使うときなど、オブジェクトのディープコピーを作りたくなることがたまにあり、
調べても自分が期待する結果になるような方法がなかったので、備忘録的にまとめておこうと思います。間違いやもっと良い方法などありましたら、コメントいただけると嬉しいです!
実現したいこと
下記のように Array の中に複数オブジェクトがあるデータを良い感じにコピーしたい...
[{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
なぜディープコピーする必要があるか
上記をコピーしてコピー先のオブジェクトを変更してみると...
let lists = [{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
let copyLists = [...lists]
lists[0].key1 = "hoge"
console.log({lists})
console.log({copyLists})
実行結果(一部抜粋)
lists: Array(2)
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}
copyLists: Array(2)
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}
このようにコピー先のオブジェクトを変更したところ、コピー元のオブジェクトまで更新されてしまいました。上記のようなコピーの仕方の場合、 lists と copyLists がメモリ上の同じデータを参照してしまっているためです。(細かいところは自分も理解し切れていないのですが、上記の場合はコピーして別のオブジェクトを作ったというより、同じオブジェクトに対して別名を付けているようなもの?と理解しています)
一方ディープコピーの場合は、オブジェクトとメモリ上のデータの両方をコピーするため、独立したオブジェクトを作ることができます。
ディープコピーを試してみた
方法1
ネットで検索するとよく出てくるのが、 JSON.parse(JSON.stringify()) を使用する方法。
まずはこの方法を試してみました。
let lists = [{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
let copyLists = JSON.parse(JSON.stringify(lists))
lists[0].key1 = "hoge"
console.log({lists})
console.log({copyLists})
実行結果(一部抜粋)
lists: Array(2)
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}
copyLists: Array(2)
0: {key1: "value1", key2: "value2"}
1: {key1: "value3"}
確かにコピー先のオブジェクトを変更しても、コピー元のオブジェクトは更新されなくなりましたが key2 がなくなっていることから、値が undefined の場合はキーごと消えてしまうようです...
調べてみたところ、こちらの記事で解説があり、どうやら特定のオブジェクトのプロパティだった場合、消されてしまうようです。
方法2
Array.prototype.map() で新しいオブジェクトを作るパターン
let lists = [{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
let copyLists = lists.map( list => ({'key1': list.key1, 'key2': list.key2}))
lists[0].key1 = "hoge"
console.log({lists})
console.log({copyLists})
実行結果(一部抜粋)
lists: Array(2)
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}
copyLists: Array(2)
0: {key1: "value1", key2: "value2"}
1: {key1: "value3", key2: undefined}
コピー先のオブジェクトを変更しても、コピー元のオブジェクトは更新されず、値が undefined になっているオブジェクトもディープコピーできました!
let lists = [{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
let copyLists = lists.map( list => ({...list}))
lists[0].key1 = "hoge"
console.log({lists})
console.log({copyLists})
実行結果(一部抜粋)
lists: Array(2)
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}
copyLists: Array(2)
0: {key1: "value1", key2: "value2"}
1: {key1: "value3", key2: undefined}
スプレット構文を使った方法であれば、仮に key が変わった場合でも、修正が不要になるので良さそうです!
方法3
lodash を使う方法
予め lodash をインストールしておきます!
npm i --save lodash
こちらでインポートします!
import _ from 'lodash'
lodash の cloneDeep を使ったディープコピー
let lists = [{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
let copyLists = _.cloneDeep(lists)
lists[0].key1 = "hoge"
console.log({lists})
console.log({copyLists})
実行結果(一部抜粋)
lists: Array(2)
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}
copyLists: Array(2)
0: {key1: "value1", key2: "value2"}
1: {key1: "value3", key2: undefined}
lodash を別途インストールする必要はありますが、
・実装が容易になる
・他の方法と違い、深い階層までディープコピーできる
以上の点から今後はこちらの方法で実装していこうと思います!
まとめ
この辺は地味にハマるところなので、徐々に理解を深めていきたいと思いました...
Discussion
前にこんなの書きました。
ぜひ自作を。シンプルなものは簡単ですよ。
JavaScript さまざまな型に対応できる拡張機能つき clone と cloneDeep を実装しました。 - Qiita
ありがとうございます!
確かに実際に実装してみることでさらに理解が深まりそうですね!
参考にさせていただきます!