Javascript オブジェクトの浅い/深いコピー
概要
Javascript でオブジェクトをコピーしたい場合、Object.assign()メソッドが使えます。Object.assign()メソッドはいわゆる浅いコピーとなっていて、注意が必要です。
浅いコピーで何が問題になるのかを説明します。
浅いコピーの問題点
ゲーム制作を例に取り上げましょう。
出現するモンスターの特徴をJavascript のオブジェクトで管理しているとします。
モンスターの名前はスライム、色は青です。モンスターには武器を複数持たせることができて、今回はダメージが100の剣を装備しています。
const monster = {
name: "スライム",
color: "青",
weapons:[
{name:"剣", damage:100}
]
}
絵柄を使い回すこともありますので、コピーして闇スライムをつくりましょう。
const monster = {
name: "スライム",
color: "青",
weapons:[
{name:"剣", damage:100}
]
}
const darkMonster = Object.assign({},monster);
darkMonster.name = "闇スライム";
darkMonster.color = "黒";
console.log(monster);
/*
{
"name":"スライム",
"color":"青",
"weapons":[{"name":"剣","damage":100}]
}
*/
console.log(darkMonster);
/*
{
"name":"闇スライム",
"color":"黒",
"weapons":[{"name":"剣","damage":100}]
}
闇スライムは魔法を使えそうなので、魔法の杖を装備させます。
const monster = {
name: "スライム",
color: "青",
weapons:[
{name:"剣", damage:100}
]
}
// 浅いコピー
const darkMonster = Object.assign({},monster);
darkMonster.name = "闇スライム";
darkMonster.color = "黒";
darkMonster.weapons[0] = {name:"魔法の杖", damage: 200}; // 追加
console.log(monster);
/*
{
"name":"スライム",
"color":"青",
"weapons":[{name:"魔法の杖", damage: 200};]
}
*/
console.log(darkMonster);
/*
{
"name":"闇スライム",
"color":"黒",
"weapons":[{name:"魔法の杖", damage: 200};]
}
問題発生です!!!
闇スライムの武器を変更したら、つられて通常スライムの武器まで変更されてしまいました。
通常スライムの武器は変更していないにもかかわらず、です。
これはObject.assign()が浅いコピーであることが原因です。
浅いコピーでは、オブジェクトが保有する直接の値しかコピーされません。
nameプロパティ、colorプロパティはそのままコピーされますが、
weaponsプロパティは配列への参照を持っているだけなので、参照がコピーされます。
(weaponsプロパティの値は、結局はスライムも闇スライムも同じ場所を見ることになる)
要するにObject.assing()でのコピーは、オブジェクトの浅い部分までは面倒みるけど、深いところまではいちいちコピーしないからね!ということなのです。
深いコピー
深いコピーをするためにはJSON.stringify() メソッドと、JSON.parse() メソッドを上手く使います。JSON.stringify() は、javasctiptのオブジェクトをJSON文字列に変換するメソッドで、JSON.parse() はその逆です。
const monster = {
name: "スライム",
color: "青",
weapons:[
{name:"剣", damage:100}
]
}
// 深いコピー
const darkMonster = JSON.parse(JSON.stringify(monster));
darkMonster.name = "闇スライム";
darkMonster.color = "黒";
darkMonster.weapons[0] = {name:"魔法の杖", damage: 200}; // 追加
console.log(monster);
/*
{
"name":"スライム",
"color":"青",
"weapons":[{name:"剣", damage:100};]
}
*/
console.log(darkMonster);
/*
{
"name":"闇スライム",
"color":"黒",
"weapons":[{name:"魔法の杖", damage: 200};]
}
これで上手くいきました!
lodash で深いコピー
lodash は、便利な関数を集めたライブラリです。実際にJavascript のプロジェクトにはよく導入されている気がします。lodash で深いコピーをするには以下のようになります。
// 深いコピー
const darkMonster = _.cloneDeep(monster);
まとめ
- Object.assign() は浅いコピー(直接保有の値までしかコピーされない)
- 深いコピーをするにはJSON.stringify() とJSON.parce() を上手く使う
- 深いコピーをするにはlodash を使う