【javascript】オブジェクトのコピーは沼にハマるから気をつけろ
vue.jsの状態管理ライブラリ『Vuex』を使っていたときに、オブジェクトの操作で沼ったので整理するために書きました。
TL;DR
オブジェクトのコピーは、shallow copy(浅いコピー)とdeep copy(深いコピー)の2種類があることを理解しておこう。
困った時はJSON.parse(JSON.stringify(obj))で解決だ!
const origin1 = { id: 0, name: "本家" };
let shallow = origin1;
shallow.name = "あさい";
console.log(shallow); // { id: 0, name: "あさい" }
console.log(origin1); // { id: 0, name: "あさい" } ←コピー元も変わってしまう
const origin2 = { id: 0, name: "本家" };
let deep = JSON.parse(JSON.stringify(origin2));
deep.name = "ふかい";
console.log(deep); // { id: 0, name: "本家" }
console.log(origin2); // { id: 0, name: "ふかい" } ←コピー元はそのまま
これら2つの差は、メモリまでコピーするかしないかです。
shallow copyはオブジェクトだけコピーし、コピー元とコピー先はどちらとも同じメモリ上を見ている。
deep copyはメモリもコピーし、他のメモリ上にデータを複製するのでコピー先ではコミー元のメモリは参照しない。
安直に代入してしまっていると、気付かぬうちにコピー元まで変更してしまっているケースも…
これが原因でバグが発生してしまうかもしれないので、shallow copyとdeep copyの違いを理解して使えると良いですね。
shallow copy(浅いコピー)
オブジェクトをコピーするときに、安直にそのまま代入してしまうと『shallow copy』(浅いコピー)になってしまいます。
shallow copyとは何かというと、コピー元とコピー先両方が同じメモリを参照している状態のことを言います。
イメージとしては、『勤怠打刻のテンプレートをGoogleDrive上でみんなで共有していて、これを各自コピーしたものに入力をして使っている』場面を想像してみてください。shallow copyの場合だと、ローカルに落としたコピーに入力しているはずが、知らぬ間にオリジナルのテンプレートにも記入されていることに…
これでは汎用性に欠けますよね?
deep copy(深いコピー)
では、同じメモリを参照しないようにすれば解決できそうです。
そこでオブジェクトだけでなく、メモリ上のデータも他の部分にコピーしてしまえば良いじゃないかというのが『deep copy(深いコピー)』です。
先程のイメージに当てはめると、普段使っているGoogle Driveはdeep copyですよね?
Google Drive上のテンプレートが置かれているのは『Googleのサーバー上のAAAAAというメモリ上の場所』。
同じGoogle Drive上にコピーを作成したとしても、コピーの場所は『Googleのサーバー上のBBBBBというメモリ上』。自分のPCにダウンロードしてきたら『MAC上のXXXXXというメモリ上にコピーが置かれる』。
これらは1つ1つ、別のメモリを参照しています。
これがdeep copyです。
shallow copyとdeep copyの比較
では、実際にコードベースで見てみます。(コードはTL;DRに載せたものと同じです)
const origin1 = { id: 0, name: "本家" };
let shallow = origin1;
shallow.name = "あさい";
console.log(shallow); // { id: 0, name: "あさい" }
console.log(origin1); // { id: 0, name: "あさい" } ←コピー元も変わってしまう
const origin2 = { id: 0, name: "本家" };
let deep = JSON.parse(JSON.stringify(origin2));
deep.name = "ふかい";
console.log(deep); // { id: 0, name: "本家" }
console.log(origin2); // { id: 0, name: "ふかい" } ←コピー元はそのまま
shallow copyだとコピー元もnameが『”本家” → “あさい”』に書き換えられているのがわかります。
deep copyはJSON.parse(JSON.stringify(obj))を使うことでできます。
(なぜこれがdeep copyになるかというと、一度文字列にすることで別のものとして扱われるとかなんとか。)
deep copyはコピー元が書き変わっていないことがわかります。
結論
安直にオブジェクトをコピーしていると、知らぬ間にコピー元も変更していてバグとなってしまうかもしれません。
shallow copyとdeep copyそれぞれ理解した上で使うよう心がけましょう!
zawa1205
webフロントエンドエンジニア