JavaScriptでオブジェクトをコピーする方法まとめ（多次元オブジェクト含む）

# 最初に
JavaScriptの配列をコピーする方法については、[こちらの記事](https://qiita.com/chimeiwang/items/1447baf170f2539795a0)をクリックしてください。
本記事は、オブジェクトのコピー方法を紹介していきます。

プリミティブ型（string・number・booleanなと）と異なって、オブジェクト（配列・関数を含む）はノンプリミティブ型ですので、`x = y`でコピーすると参照渡しのコピーになってしまいます。

例えば、以下のようなコードになります。

```js
// プリミティブ型
let name = "Kimmy"
let anotherName = name
anotherName = "Qiita"

// 値渡しのコピーなのでanotherNameの値は変更されていません
console.log(name)  // Kimmy
console.log(anotherName)  //Qiita

// ノンプリミティブ型
let person = {name: "Kimmy", job: "Wizard"}
let anotherPerson = person
anotherPerson.name = "Qiita"

// 参照渡しのコピーなので、一方を変更すると両方に変更が反映されてしまいます
console.log(person)  // {name: "Qiita", job: "Wizard"}
console.log(anotherPerson)  // {name: "Qiita", job: "Wizard"}
```

# コピー方法について
コピーには浅いコピーと深いコピー２つがあります。
- **浅いコピー**：１段階のデータのみ値渡しのコピーになり、２段階以上のデータには参照渡しのコピーになります。
- **深いコピー**：多階層のデータでも、オブジェクトは最も深いところまですべての値をコピーします。
なので、オブジェクトの深さを考える必要があります。

# 浅いコピー（Shallow Copy）の方法

以下`person`のような階層を持っていないオブジェクトで例を挙げます。

## `Object.assign()`

```js
let person = {name: "Kimmy", job: "Wizard"}
let personCopy =  Object.assign({}, person)

personCopy.name = "Qiita";

console.log(person); // {name: "Kimmy", job: "Wizard"}
console.log(personCopy); // {name: "Qiita", job: "Wizard"}
```

## スプレッド構文
ES2015で追加されたスプレッド構文を利用した浅いコピーができます。

```js
let person = {name: "Kimmy", job: "Wizard"}
let personCopy = {...person};

personCopy.name = "Qiita";

console.log(person); // {name: "Kimmy", job: "Wizard"}
console.log(personCopy); // {name: "Qiita", job: "Wizard"}
```

# 深いコピー（Deep Copy）の方法
JavaScriptライブラリー（[Lodash](https://lodash.com/)や[Ramda](https://ramdajs.com/)など）を使えば、深いコピーは簡単にできますが、今回はライブラリーなしの方法を紹介します。

## JSON.parse/stringify
`JSON.parse()`と`JSON.stringify()`をあわせて利用します。

`JSON.stringify()`メソッドで JavaScript のオブジェクトや値を JSON 文字列に変換してから、`JSON.parse()`メソッドで文字列をJSONとして解析し、文字列によって記述されている値やオブジェクトを構築します。

ここで、注意すべきなのは、JSONでは配列と数値・文字列・真偽値・null、及びそれらを値として持つオブジェクトのみを変換することができます。ですので、Dates・undefined・functions・RegExpsなど値が持つオブジェクトに対してはこの方法を使ってコピーを行うことはできません。

### データが破損された例

```js
const sampleObject = {
  string: 'example',
  number: 555,
  boolean: false,
  null: null,
  notANumber: NaN,
  date: new Date('2021-03-17T23:59:59'), 
  undefined: undefined, 
  infinity: Infinity,
  regExp: /.*/
};

const faultyCopy = JSON.parse(JSON.stringify(sampleObject))

console.log(sampleObject)
console.log(faultyCopy)
```

結果の比較は以下の通りです。

```js

// sampleObject
{  
  string: "example",
  number: 555,
  boolean: false,
  null: null,
  notANumber: NaN,
  date: Wed Mar 17 2021 23:59:59 GMT+0900 (Japan Standard Time),
  undefined: undefined,
  infinity: Infinity,
  regExp: /.*/
}

// faultyClone
{
  string: "example",
  number: 555,
  boolean: false,
  null: null,
  notANumber: null, // NaNが破損され、値は'null'になる
  date: "2021-03-17T14:59:59.000Z", // Dateの値は文字列になる
　 // undefined: undefinedのキーと値が破損される
  infinity: null, // 破損され、値のInfinityは'null'になる
  regExp: {} // 破損され、値は'{}'になる
}
```
### 成功コピー例

```js
let person = {
  name: "Kimmy", 
  job: "Wizard", 
  age: 70, 
  guildMember: false, 
  legendaryWeapon: null, 
  battleRecord: {
   BlackWater: 10, 
   SkyCastle: 3 
  }
}

let personCopy = JSON.parse(JSON.stringify(person))

personCopy.battleRecord.SkyCastle = 100;

console.log(person);
console.log(personCopy);
```

結果として、`person`の値はそのまま変わっていません。

```
// person
{
  name: "Kimmy", 
  job: "Wizard", 
  age: 70, 
  guildMember: false, 
  legendaryWeapon: null, 
  battleRecord: {
   BlackWater: 10, 
   SkyCastle: 3 
  }
}

// personCopy
{
  name: "Kimmy", 
  job: "Wizard", 
  age: 70, 
  guildMember: false, 
  legendaryWeapon: null, 
  battleRecord: {
   BlackWater: 10, 
   SkyCastle: 100 
  }
}
```

## 関数を作る

```js
function cloneObject(obj) {
  let clone = {};
  Object.keys(obj).forEach((key) => {
    obj[key] != null && typeof obj[key] === 'object'
      ? (clone[key] = cloneObject(obj[key]))
      : (clone[key] = obj[key]);
  });
  return clone;
}
```

```js
let person = {
  name: "Kimmy", 
  job: "Wizard", 
  age: 70, 
  guildMember: false, 
  legendaryWeapon: null, 
  battleRecord: {
   BlackWater: 10, 
   SkyCastle: 3 
  }
}

let personCopy = cloneObject(person)

personCopy.battleRecord.SkyCastle = 100;

console.log(person);
console.log(personCopy);
```

結果として、`person`の値が変更されません。

```
// person
{
  name: "Kimmy", 
  job: "Wizard", 
  age: 70, 
  guildMember: false, 
  legendaryWeapon: null, 
  battleRecord: {
   BlackWater: 10, 
   SkyCastle: 3 
  }
}

// personCopy
{
  name: "Kimmy", 
  job: "Wizard", 
  age: 70, 
  guildMember: false, 
  legendaryWeapon: null, 
  battleRecord: {
   BlackWater: 10, 
   SkyCastle: 100 
  }
}
```

#最後に
ここまでお読みいただきありがとうございました。
他の方法がございましたら、ぜひコメント欄にシェアしてください。
また、文法ミスや誤字がありましたら、編集リクエストでご意見をいただければと思います。