LoginSignup
108
97

More than 3 years have passed since last update.

JavaScriptでディープコピーしたい時

Last updated at Posted at 2020-05-15

はじめに

モーダルを使うときなど、オブジェクトのディープコピーを作りたくなることがたまにあり、
調べても自分が期待する結果になるような方法がなかったので、備忘録的にまとめておこうと思います。
間違いやもっと良い方法などありましたら、コメントいただけると嬉しいです!

実現したいこと

下記のようにArrayの中に複数オブジェクトがあるデータを良い感じにコピーしたい。。

[{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]

なぜディープコピーする必要があるか

上記をコピーしてコピー先のオブジェクトを変更してみると・・
追記(2020/5/16)
@jay-es さんからコメントをいただき、シャローコピーするように修正しました!
コメントありがとうございます!

let lists = [{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
let copyLists = [...lists]
copyLists[0].key1 = "hoge"
console.log('lists')
console.log(lists)

console.log('copyLists')
console.log(copyLists)

実行結果

lists
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}

copyLists
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))

copyLists[0].key1 = "hoge"
console.log('lists')
console.log(lists)

console.log('copyLists')
console.log(copyLists)

実行結果

lists
0: {key1: "value1", key2: "value2"}
1: {key1: "value3", key2: undefined}

copyLists
0: {key1: "hoge", 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}))
copyLists[0].key1 = "hoge"
console.log('lists')
console.log(lists)

console.log('copyLists')
console.log(copyLists)

実行結果

lists
0: {key1: "value1", key2: "value2"}
1: {key1: "value3", key2: undefined}

copyLists
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}

コピー先のオブジェクトを変更しても、コピー元のオブジェクトは更新されず、
値がundefinedになっているオブジェクトもディープコピーできました!

追記(2020/5/16)
Array.prototype.map()とスプレット構文を使う方法
@pochopocho13 さんからコメントで教えていただきました!ありがとうございます!)

let lists = [{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
let copyLists = lists.map( list => ({...list}))
copyLists[0].key1 = "hoge"
console.log('lists')
console.log(lists)

console.log('copyLists')
console.log(copyLists)

実行結果

lists
0: {key1: "value1", key2: "value2"}
1: {key1: "value3", key2: undefined}

copyLists
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}

スプレット構文を使った方法であれば、仮にkeyが変わった場合でも、
修正が不要になるので良さそうです!

方法3

lodashを使う方法

追記(2020/5/17)
@standard-software さんからこちらの方法を教えていただきました!
ありがとうございます!

予めlodashをインストールしておきます!

npm i --save lodash

こちらでインポートします!

import _ from 'lodash'

lodashのcloneDeepを使ったディープコピー

let lists = [{'key1': 'value1', 'key2':'value2'},{'key1': 'value3', 'key2': undefined}]
let copyLists = _.cloneDeep(lists)
copyLists[0].key1 = "hoge"
console.log('lists')
console.log(lists)

console.log('copyLists')
console.log(copyLists)

実行結果

lists
0: {key1: "value1", key2: "value2"}
1: {key1: "value3", key2: undefined}

copyLists
0: {key1: "hoge", key2: "value2"}
1: {key1: "value3", key2: undefined}

lodashを別途インストールする必要はありますが、
・実装が容易になる
・他の方法と違い、深い階層までディープコピーできる
以上の点から今後はこちらの方法で実装していこうと思います!

まとめ

この辺は地味にハマるところなので、徐々に理解を深めていきたいと思いました。。

参考にさせていただいた記事

JavaScriptのDeepCopyでJSON.parse/stringifyを使ってはいけない
[JavaScript]色々なディープコピー

108
97
10

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
108
97