JavaScriptで連想配列(オブジェクト)のコピーをする正しい方法

JavaScriptでよく使うデータ型と言えば連想配列(あるいはオブジェクトとも言う)

これをコピーする場合、間違った方法を使うとバグや不具合の原因になります。しかもエラーも出ないので自分でミスにも気づきにくいのが厄介な所です。

ここでは連想配列をコピーする本当に正しい方法とコード例についてまとめました。

連想配列コピーのNG例

まず連想配列コピーでやってはいけないNG例を紹介します。

それはある変数に連想配列をそのまま代入してしまうこと

これをやってしまうとコピー元とコピー先のデータが共有されてしまいます。

 

例えば次のように連想配列をコピーした(つもり)のコード例で考えてみましょう。

連想配列のコピー元が original  、コピー先が cloned  です。

コードとしては間違ってはいないですが、データそのものをコピーする場合は正しくありません。

 

どういう風に間違っているかは次のコードを実行してみれば分かるはず

コピー先の連想配列の値を変更した後 original  と cloned  をコンソール表示するコード例です。

表示結果はそれぞれ次の通り・・・

まず次がコピー元 original  のコンソール表示、

そして次がコピー先 cloned  のコンソール表示

・・・見てわかるように両方とも同じ内容に書き換わってしまっています。

 

こうなる理由は連想配列のコピーのされ方に次の2種類があるため

  • 浅い(シャロー)コピー【Shallow Copy】
  • 深い(ディープ)コピー【Deep Copy】

浅いコピーの場合はオブジェクトの参照(データの置き場所)が共有されてしまうのに対し、深いコピーの場合は内部のデータそのものまで全部コピーされます。

コード例のようにただ代入でコピーしてしまうと浅いコピーが起きてしまうので、一方を変更すると両方に変更が反映されてしまうという訳です。

浅いコピーは階層を持たない配列とか数値とかのコピーを表すのに対し、深いコピーはオブジェクト内にオブジェクトの入ってるデータや2階層以上の階層を持つオブジェクトのコピーを表す訳です。

※ コメント欄で間違いを指摘してくださった方、ありがとうございます m(__)m

なので完全に連想配列をコピーするにはディープコピーする必要があります。

連想配列をディープコピーするには

ではどうやって連想配列をディープコピーするのか・・・ということですがやり方は簡単

それは Object.create  という連想配列専用のメソッドを使うことです。

例えばコピー元を original  とすれば次のように書けばOK

これで cloned  が original  のディープコピーになります。

 

例えば次が Object.create  を使って連想配列をコピーしているコード例

先ほどのコードと同じく original  と cloned  をコンソール表示しています。

このコードを実行したときの結果はそれぞれ次の通り・・・

まず次がコピー元の連想配列 original  のコンソール表示、

そして次がコピー先の連想配列 cloned  のコンソール表示

・・・今度は無事 cloned  の変更が original  に影響を与えないようになりました。

 

以上が連想配列をディープコピーする方法です。

浅いコピーは意図的でない限りほとんど使わないので基本的にはディープコピーした方がバグや不具合の温床になりにくいと思います。

訂正 : 正しくはObject.assignを使う方がいい

コメント欄で何度かご指摘がありました。

やはり Object.create を使うのが間違いで、これはコピーするものではなくプロトタイプチェーンを作成するためのものです。正しいのは Object.assign  を使うことですね。

▼ Object.assignについての解説

Object.assign() メソッドは、すべての列挙可能な自身のプロパティの値を、1つ以上のコピー元オブジェクトからコピー先オブジェクトにコピーするために使用されます。

引用元 : https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

▼ これを使ったオブジェクトコピー例

▼ コンソールへの出力結果

このコードの Object.assign({}, obj1);  のように大1引数に空オブジェクト、第2引数にコピー元オブジェクトを渡してあげればOKです。本当にご指摘ありがとうございます。m(__)m

配列をコピーする場合もディープコピーが必要

ここまでで連想配列をコピーする方法についてまとめてきました。

コピー種類にはシャローとディープの2種類があり、データも含めて完全にコピーするにはディープコピーが必要なことを忘れないようにしたいですね。

 

ちなみに配列を完全コピーする場合もディープコピーが必要になります。

そのやり方やコード例は次でまとめた通り

連想配列と同じく配列コピーする場合もコピー方法には要注意です。

ここまでのまとめ

ということで連想配列のコピー方法まとめ

  • 代入でコピーすると浅いコピーになる
    連想配列を別屁数に直接代入すると浅いコピー(Shallow Copy)になってしまう。
    参照が同じなので一方を変更するともう一方も変更されてしまう点に要注意!
  • Object.createを使えば深いコピーになる
    連想配列内のデータも含め全部コピーするには Object.create  を使うことが必要
    参照が別々になるので一方が変更されてももう一方に影響を及ぼさない

場合によっては意図的に浅いコピーが必要になる場面もあるかもしれません。

しかし基本的には Object.create  を使うのが確実で安全な方法だと思います。

以上JavaScriptで連想配列をコピーする方法についてでした。

Shareこの記事をシェアしよう!

Commentsこの記事についたコメント

9件のコメント
  • satoyama

    JSでオブジェクトのことを連想配列とは呼ばないでほしいです。

    10月 10, 2019 11:02 am
    • ぴー助

      コメントありがとうございます!

      確かにJavaScriptのオブジェクトは連想配列と呼ぶには少し不適切かもしれません・・・
      ですが他言語(たとえばPHPなど)での連想配列と同じ使い方ができるという意味で “連想配列” と呼んでいます。またGoogleサジェスト的には「javascript 連想配列 コピー」などのように “連想配列” という検索が一般的なので、それに合わせています。

      10月 10, 2019 12:28 pm
  • satoyama

    あと、シャローコピーとディープコピーの意味も全て間違ってますね。
    修正したほうがいいと思います。

    参照を変数に入れることをシャローコピーというのではなく
    一段回のコピーをシャローコピーといいます。

    ディープコピーは、オブジェクトの中にオブジェクトが入っているもの
    あるいは、3段階も4段階も中にオブジェクトが入っているものをコピーすることを言います。

    10月 10, 2019 11:06 am
    • ぴー助

      これについては勉強不足でした。

      確かに次のページなどを見ると、このように書いてあります。

      ===============================

      シャローコピー(浅いコピー)はプリミティブ値(文字列、数値、真偽値、null、undefined、Symbol)をコピーするが、それ以外のオブジェクトは参照をコピーする。参照がコピーされるということは、コピー元とコピー先でオブジェクトが共有されるということである。
      一方、ディープコピー(深いコピー)はプリミティブ値だけでなく、オブジェクトも値としてコピーする。したがって、コピー元とコピー先のオブジェクトは別物である。

      参照元 : https://nansystem.com/shallow-copy-vs-deep-copy/

      ==============================

      また海外のサイトでも、このように説明されています。

      ==============================

      An object is said to be shallow copied when the source top-level properties are copied without any reference and there exists a source property whose value is an object and is copied as a reference.

      A deep copy will duplicate every object it encounters. The copy and the original object will not share anything, so it will be a copy of the original.

      参照元 : https://medium.com/@arunrajeevan/shallow-copy-vs-deep-copy-in-javascript-5ce718725a0

      ==============================

      あいまいな理解で記事を書いてしまったので、いつか記事修正したいと思います。
      間違いを指摘していただき、本当にありがとうございます。m(__)m

      10月 10, 2019 12:47 pm
  • みやびプリン

    はじめまして。
    ものすごーく細かいことを言うと、コピーという言葉さえ、値に対する参照しかないJavaScriptにおいては、違う気がします。
    値だろうが、オブジェクトだろうが、
    変数は、代入の際は、新たに値を(オブジェクトの場合は{}自体)メモリに格納し、その参照となるわけですから。
    (変数から変数への代入の場合は、各々が値対参照となる)
    値自体をコピーしてるのではなく、同じ値をメモリに別格納し、その参照をしているだけですからね。
    ただ、コピーと言った方が通りがいいとは思うので、そのままでいいかとは思います。
    間違っていたらすみません。僕が調べたり勉強した限りでは、そういった仕様に思います。

    12月 12, 2019 3:35 pm
  • ah

    Object.create はコピーしているわけではなく,プロトタイプチェーンを作るものです。
    その点「一方が変更されてももう一方に影響を及ぼさない」は不正確で
    確かにclonedを変更してもoriginalは影響しませんが,
    originalを変更すると,cloned に影響します。

    5月 11, 2020 10:17 pm
  • mm

    上記ahさんが指摘されている通りだと思いますが、
    >Object.create はコピーしているわけではなく,プロトタイプチェーンを作るものです。
    Object.createは連想配列(マップ型)のコピーではないですね……。
    今のchromeの開発者ツールからconsole.logで表示させてみると、originalとcloneが違う表記で出力されることからもわかります。

    また、Object.createで作った方はJSON.stringify()に入れて配列をJSONの文字列にしようとしたとき空で生成されてしまいますが、これはMap型ではないからですね。このように動作が変わってくるので、コピーではなく別物ということには注意した方が良いかと思います。

    10月 25, 2020 1:48 pm
    • mm

      マップ型はまたこれはこれで別ものですね…、混乱させることを書いてすみません

      10月 25, 2020 10:49 pm
      • ぴー助

        コメントありがとうございます。Object.create() がオブジェクトのコピーを作るものではない、ということは重要なので訂正しました。やはり Object.assign() を使うのが一番なようです。ご指摘に感謝します。m(__)m

        10月 26, 2020 3:13 pm

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA


このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください