配列にオブジェクトがあるときのディープコピー方法

執筆日: 2021-08-16

概要

JavaScriptで一回はつまずく部分が参照ではないでしょうか。
値渡しと参照渡しを混同してしまうと、正常に値が変更されなかったり予期せぬデータの上書きが起こりかねません。

ここでは複合型、つまり配列内にオブジェクトが存在している場合の完全コピーの方法について紹介します。

検証コード

todoオブジェクトに対し、titleを変更してみました。

望むのはそれぞれでtitle01とchange title01が出力されることです。
しかし、コピー先のtitleが元のtodoオブジェクトまで影響しており、両方タイトルが変更してしまっています。

const todo = [
    {
        id: 1,
        title: 'title01',
        description: 'description01'
    }
]
// コピーしたはずなのに...
const copyTodo = [...todo]
copyTodo[0].title = 'change title01'

console.log(todo[0].title)
// change title01
console.log(copyTodo[0].title)
// change title01

コピーできない根拠

原因は、配列まではコピーできてもオブジェクトはコピーできていないからです。
スプレッド構文はシャローコピーであり、1階層までしかコピーできません。sliceメソッドやObject.assignメソッドも同じくです。

ならば配列内のオブジェクトも含めたコピーができるディープコピーが必要になってきます。

JSON.parse(JSON.stringify())を使用する

...(省略)
const copyTodo = JSON.parse(JSON.stringify(todo))
copyTodo[0].title = 'change title01'

console.log(todo[0].title)
// title01
console.log(copyTodo[0].title)
// change title01

これはよく知られている方法ではないでしょうか。
ディープコピーとなるので、参照ではなく完全にコピーしたことになります。

mapメソッドとスプレッド構文

上記の方法でも良いのですが、このような方法もあります。
mapメソッドとスプレッド構文を使用した方法です。

...(省略)
const copyTodo = todo.map(x => { return  { ...x }})
copyTodo[0].title = 'change title01'

console.log(todo[0].title)
console.log(copyTodo[0].title)

している内容は簡単です。

  • mapにて配列内の要素を展開する
  • 展開した要素をスプレッド構文でシャローコピーする

lodashを使用する

lodashライブラリを使用すれば簡単にコピーできます。
ここでは詳しく説明しませんので、下記のURLから参照してください。
https://lodash.com/docs/#cloneDeep

結論

JavaScriptを使用している中で自分もよくつまづく部分です。
特にReactやVueの状態管理などでは検知方法として参照渡しでは反映されず、新たなオブジェクトを渡す必要があったりします。

意識してコードを書いていく必要がありますね。

運営について

Natural Tearoomはシステム開発会社フロントエンドエンジニアがんちゃんが運営するメディアです。
フロントエンド技術を中心に発信しています。

· プライバシーポリシー

SNS

© 2021 天然珈琲店