zawatech

【javascript】オブジェクトのコピーは沼にハマるから気をつけろ

タグ
javascriptjs
カテゴリ
Javascript
Xこの記事をポストする
この記事をLINEでシェア

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それぞれ理解した上で使うよう心がけましょう!

Xこの記事をポストする
この記事をLINEでシェア
zawa1205

zawa1205

Qiita
GitHub
mail

webフロントエンドエンジニア