k01ken’s b10g

He110 W0r1d!

JavaScriptでオブジェクトのコピーをする際の注意点

開発環境は、Windows10 64bit(Pro) + Firefox 69.0(64bit)。

書くのが楽だからといってオブジェクトを直接入れてしまった場合、obj_aの値を書き換えると、obj_bの値も変わってしまう。これは、シャローコピーと言って、単に参照を渡しているだけだから、そうなってしまう。例えば、以下のようなコード。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: "baseball" };
obj_a = obj_b;
obj_a.age = 30;
console.log(obj_b.age); // 30

対策としては、調べたところ、2つの方法があったので、メモする。
1つは、Object.assignを使う方法。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: "baseball" };
obj_a = Object.assign({}, obj_b);
obj_a.age = 30;
console.log(obj_b.age); // 13

もう1つは、Object.createを使う方法。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: "baseball" };
obj_a = Object.create(obj_b);
obj_a.age = 30;
console.log(obj_b.age); // 13

自分が作っている際にきちんとコピーできた際は、Object.assignだったらうまくいったのに、Object.createだと、なぜかうまくいきませんでした。
基本的には、Object.assignの方を使った方が良いみたいです。


ただ、これらの方法にも、問題があって、オブジェクト内にオブジェクトがあった場合は、参照を渡すことになってしまう。例えば、以下のようなコード。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: {sports: "baseball"} };
obj_a = Object.assign({},obj_b);
obj_a.favorite.sports = "golf";
console.log(obj_b.favorite.sports); // golf
let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: {sports: "baseball"} };
obj_a = Object.create(obj_b);
obj_a.favorite.sports = "golf";
console.log(obj_b.favorite.sports); // golf


もし、オブジェクト内のオブジェクトも、コピーする場合は、オブジェクト内の値を、1つ1つ再帰処理で抜き出して、新しく作ったオブジェクトに入れていき、そのオブジェクトを最終的に返すような関数を作らないといけない。一応、書けるところまで書いてみました。階層があまりに深くて、再帰処理を何度も呼び出すわけじゃない限り、これで大丈夫と思うけど、何かエラーがあったら報告お願いします。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: {sports: "baseball"} };

createNewObject = (obj) => {
  let result = {};
  if(typeof obj === 'object' && !Array.isArray(obj)){ 
    extract = (no, o) => {
      for(let item in o){
        if(typeof o[item] === 'object' && !Array.isArray(o[item])){
          no[item] = {};
          extract(no[item], o[item]);
        }else if(typeof o[item] === 'object' && Array.isArray(o[item])){
          no[item] = [];
          extract(no[item], o[item]);
        }else{
          no[item] = o[item];
        }
      }
    }
    extract(result, obj);
  }else{
    return false;
  }
  
  return result;
};

obj_a = createNewObject(obj_b);
obj_a.favorite.sports = "golf";
console.log(obj_b.favorite.sports); // baseball

方法を調べると、もっと簡単に、できる方法がありました。
JSON.stringifyを用いた後に、JSON.parseを用いれば良いみたいです。

let obj_a = {name: "taro", age: 15};
let obj_b = {name: "jiro", age: 13, favorite: {sports: "baseball"} };
obj_a = Object.assign({},JSON.parse(JSON.stringify(obj_b)));
obj_a.favorite.sports = "golf";
console.log(obj_b.favorite.sports); // baseball

■参考リンク
JavaScriptで連想配列(オブジェクト)のコピーをする正しい方法 | PisukeCode - Web開発まとめ
オブジェクトの値をコピーするObject.assign() - 30歳からのプログラミング
Object.create() - JavaScript | MDN
Object.assign() - JavaScript | MDN
Object.assign()を使ったコピーいろいろ - Qiita
【JavaScript】「Object.assign vs Object.create」のおもしろい議論みつけた | 武骨日記
javascript – Object.create()とObject.assign()を使ってオブジェクトを作成することの違いは何ですか? - コードログ