Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
50
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Javascript オブジェクトの浅い/深いコピー

Javascript オブジェクトの浅い/深いコピー

概要

Javascript でオブジェクトをコピーしたい場合、Object.assign()メソッドが使えます。Object.assign()メソッドはいわゆる浅いコピーとなっていて、注意が必要です。
浅いコピーで何が問題になるのかを説明します。

浅いコピーの問題点

ゲーム制作を例に取り上げましょう。
出現するモンスターの特徴をJavascript のオブジェクトで管理しているとします。
モンスターの名前はスライム、色は青です。モンスターには武器を複数持たせることができて、今回はダメージが100の剣を装備しています。

const monster = {
  name: "スライム",
  color: "", 
  weapons:[
    {name:"", damage:100}
  ]
}

figure1.png

絵柄を使い回すこともありますので、コピーして闇スライムをつくりましょう。

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}]
}

figure2.png

闇スライムは魔法を使えそうなので、魔法の杖を装備させます。

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};]
}

figure2.png

問題発生です!!!
闇スライムの武器を変更したら、つられて通常スライムの武器まで変更されてしまいました。
通常スライムの武器は変更していないにもかかわらず、です。
これは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};]
}

figure4.png

これで上手くいきました!

lodash で深いコピー

lodash は、便利な関数を集めたライブラリです。実際にJavascript のプロジェクトにはよく導入されている気がします。lodash で深いコピーをするには以下のようになります。

// 深いコピー
const darkMonster = _.cloneDeep(monster);

まとめ

  • Object.assign() は浅いコピー(直接保有の値までしかコピーされない)
  • 深いコピーをするにはJSON.stringify() とJSON.parce() を上手く使う
  • 深いコピーをするにはlodash を使う
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
50
Help us understand the problem. What are the problem?