調べたことメモ。
違い
- ディープコピー:オブジェクトの中のオブジェクトまで丸ごとコピーする
- シャローコピー:オブジェクトの中の中のオブジェクトは参照コピーする
シャローは「浅い」、
ディープは「深い」という意味。
シャローコピーとは?
実例で見たほうが分かりやすいと思うので、例を出します。
例えば、以下のようなオブジェクトがあるとします。
const human= { name:"tarou", age:60, myChild:{ name:"zirou", age:29 } }
「60歳のオッサンに29歳の息子がいる」なオブジェクトです。
これを以下のようにしてコピーします。
const cloneHuman = Object.assign({}, human); // Object.assign()は、オブジェクトをコピーしたりできるメソッド。 //第一引数にコピー先のオブジェクト、第二引数にコピー元のオブジェクトを指定する。
すると、cloneHumanはhumanとまったく同じ「60歳のオッサンに29歳の息子がいる」なオブジェクトになります。
ところが、これだとcloneHumanオブジェクトのmyChildオブジェクトは参照コピーされています。
例えば、cloneHumanオブジェクトのmyChildオブジェクトのageを「111」に変えてみます。
すると、humanオブジェクトのmyChildオブジェクトのageまで「111」に変わってしまいます。
▲humanオブジェクトのほうまで変わってしまっている。
図解すると以下のような感じ。
「オブジェクトの中のオブジェクトは参照コピーするようにする」というのはJavaScriptの仕様らしい。
ディープコピーとは?
オブジェクトの中にあるオブジェクトまで完全にコピーするには、自分で実装するしかないらしい。
方法①:愚直におこなう
例えば以下のようにすると、ディープコピーされます。
//シャローコピー const shallowClone = (obj) => { return Object.assign({}, obj); }; //ディープコピー(中身がオブジェクトの場合は再帰的にシャローコピーする) function deepClone(obj) { const newObj = shallowClone(obj); // プロパティがオブジェクト型であるなら、再帰的に複製する Object.keys(newObj) .filter(k => typeof newObj[k] === "object") .forEach(k => newObj[k] = deepClone(newObj[k])); return newObj; } //ディープコピーする const cloneHuman = deepClone(human);
図解すると以下のような感じ。
ディープコピーの場合は、humanオブジェクトとcloneHumanには何の関係もないので
例えば、cloneHumanオブジェクトのmyChildオブジェクトのageを「111」に変えてみても
humanオブジェクトのmyChildオブジェクトのageは「29」のまま変化しません。
方法②:シリアライズで簡易におこなう
単純にシリアライズ → デシリアライズという形でも、ディープコピーできるようです。
var cloneHuman = JSON.parse(JSON.stringify(human));
ただし、この方法の場合は以下のような欠点があるようです。
この方法の問題は、先に述べたようなStringや配列、Numberなどの独自リテラルを持つオブジェクトにしか使えない、ということです。
例えばDateオブジェクトや、jQueryオブジェクト、BigNumber.jsなどの拡張されたオブジェクトには一切使えません。これらはDOM要素を直接、参照として持っていたりするので、ライブラリの深いところまで知り尽くしていないと軽々しくコピー出来ないわけです。
しかしながら、これは大した問題にはなりません。そういったオブジェクトをディープコピーする必要は殆どありませんし、また、そういったことが必要なライブラリにはコピーする手段が必ず用意されているからです。
方法③:ライブラリを使う
Lodashなどの外部ライブラリを使えば、一発でディープコピーできるらしい。
const cloneDeep = require('cloneDeep'); const cloneHuman = cloneDeep(human);
おわり
参考にしたページ:
コメント