JavaScriptでよく使うデータ型と言えば連想配列(あるいはオブジェクトとも言う)
これをコピーする場合、間違った方法を使うとバグや不具合の原因になります。しかもエラーも出ないので自分でミスにも気づきにくいのが厄介な所です。
ここでは連想配列をコピーする本当に正しい方法とコード例についてまとめました。
連想配列コピーのNG例
まず連想配列コピーでやってはいけないNG例を紹介します。
それはある変数に連想配列をそのまま代入してしまうこと
これをやってしまうとコピー元とコピー先のデータが共有されてしまいます。
例えば次のように連想配列をコピーした(つもり)のコード例で考えてみましょう。
1 2 |
var original = {x: 1, y: 2, z: 3}; var cloned = original; |
連想配列のコピー元が original 、コピー先が cloned です。
コードとしては間違ってはいないですが、データそのものをコピーする場合は正しくありません。
どういう風に間違っているかは次のコードを実行してみれば分かるはず
1 2 3 4 5 6 |
cloned['x'] = 4; cloned['y'] = 5; cloned['z'] = 6; console.log(original); console.log(cloned); |
コピー先の連想配列の値を変更した後 original と cloned をコンソール表示するコード例です。
表示結果はそれぞれ次の通り・・・
まず次がコピー元 original のコンソール表示、
1 |
{ x: 4, y: 5, z: 6 } |
そして次がコピー先 cloned のコンソール表示
1 |
{ x: 4, y: 5, z: 6 } |
・・・見てわかるように両方とも同じ内容に書き換わってしまっています。
こうなる理由は連想配列のコピーのされ方に次の2種類があるため
- 浅い(シャロー)コピー【Shallow Copy】
- 深い(ディープ)コピー【Deep Copy】
浅いコピーの場合はオブジェクトの参照(データの置き場所)が共有されてしまうのに対し、深いコピーの場合は内部のデータそのものまで全部コピーされます。
コード例のようにただ代入でコピーしてしまうと浅いコピーが起きてしまうので、一方を変更すると両方に変更が反映されてしまうという訳です。
浅いコピーは階層を持たない配列とか数値とかのコピーを表すのに対し、深いコピーはオブジェクト内にオブジェクトの入ってるデータや2階層以上の階層を持つオブジェクトのコピーを表す訳です。
※ コメント欄で間違いを指摘してくださった方、ありがとうございます m(__)m
なので完全に連想配列をコピーするにはディープコピーする必要があります。
連想配列をディープコピーするには
ではどうやって連想配列をディープコピーするのか・・・ということですがやり方は簡単
それは Object.create という連想配列専用のメソッドを使うことです。
例えばコピー元を original とすれば次のように書けばOK
1 |
var cloned = Object.create(original); |
これで cloned が original のディープコピーになります。
例えば次が Object.create を使って連想配列をコピーしているコード例
1 2 3 4 5 6 7 8 9 |
var original = {x: 1, y: 2, z: 3}; var cloned = Object.create(original); cloned['x'] = 4; cloned['y'] = 5; cloned['z'] = 6; console.log(original); console.log(cloned); |
先ほどのコードと同じく original と cloned をコンソール表示しています。
このコードを実行したときの結果はそれぞれ次の通り・・・
まず次がコピー元の連想配列 original のコンソール表示、
1 |
{ x: 1, y: 2, z: 3 } |
そして次がコピー先の連想配列 cloned のコンソール表示
1 |
{ x: 4, y: 5, z: 6 } |
・・・今度は無事 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
▼ これを使ったオブジェクトコピー例
1 2 3 4 5 6 7 8 9 |
var obj1 = {a:1, b:'x', c:[1,2,3]}; var obj2 = Object.assign({}, obj1); obj1.a = 2; obj1.b = 'y'; obj1.c = [4,5,6]; console.log('obj1 : ', obj1); console.log('obj2 : ', obj2); |
▼ コンソールへの出力結果
1 2 3 4 5 6 7 8 9 10 |
obj1 : { a: 2 b: "y" c: (3) [4, 5, 6] } obj2 : { a: 1 b: "x" c: (3) [1, 2, 3] } |
このコードの Object.assign({}, obj1); のように大1引数に空オブジェクト、第2引数にコピー元オブジェクトを渡してあげればOKです。本当にご指摘ありがとうございます。m(__)m
配列をコピーする場合もディープコピーが必要
ここまでで連想配列をコピーする方法についてまとめてきました。
コピー種類にはシャローとディープの2種類があり、データも含めて完全にコピーするにはディープコピーが必要なことを忘れないようにしたいですね。
ちなみに配列を完全コピーする場合もディープコピーが必要になります。
そのやり方やコード例は次でまとめた通り
連想配列と同じく配列コピーする場合もコピー方法には要注意です。
ここまでのまとめ
ということで連想配列のコピー方法まとめ
- 代入でコピーすると浅いコピーになる
連想配列を別屁数に直接代入すると浅いコピー(Shallow Copy)になってしまう。
参照が同じなので一方を変更するともう一方も変更されてしまう点に要注意! - Object.createを使えば深いコピーになる
連想配列内のデータも含め全部コピーするには Object.create を使うことが必要
参照が別々になるので一方が変更されてももう一方に影響を及ぼさない
場合によっては意図的に浅いコピーが必要になる場面もあるかもしれません。
しかし基本的には Object.create を使うのが確実で安全な方法だと思います。
以上JavaScriptで連想配列をコピーする方法についてでした。