JavaScriptのObjectのDeep copyメソッドを書いてみた

# 初めに
今回は[JavaScriptのinheritanceについて part2](https://qiita.com/hu-yu/items/68c6eba22014b08d2d9d)から考えた、オブジェクトのディープコピーメソッドの記録です。

まだ予想外もの（undefined, null）の排除の仕方は考えていませんが、これから思い付くときまた更新します。
今回はオブジェクトの中、プリミティブ、配列、関数、オブジェクトが乱立しているのを想定して、ディープコピーをどうやっていくか、試してみたいと思います。

まずはJavaScriptで、**Shallow copy**と**Deep copy**の違いの説明から入りたいと思います。
# Shallow copy vs. Deep copy
- **Shallow copy：浅いコピー。コピーと本体が互いを影響しあうコピー方法**
- **Deep copy：深いコピー。コピーと本体がそれぞれ独立しているというコピー方法。**

でもそもそもなぜコピーなのに、片方の操作でもう片方も中身が影響されるのです？

JavaScriptでは**pass by value（call by value）** という形式で値をアクセスするんです。
プリミティブのassignやaccessは、それぞれの値を直接にアクセスすればいいのですが、
オブジェクトのassignやaccessは、Reference（参照先）を参考してアクセスするから、
複数以上の変数にこのReference（参照先）という値を保存することができます。

しかし同じ参照先での中身の操作が、ほかに同じ参照先を保存した変数にも必ず影響する。
なのでこのReference（参照先）のアクセスの仕方を**pass by reference** とよく思われているのですが、実際はJavaScriptでは**pass by value**だけ、そしてプリミティブのアクセスと区別するために**pass by sharing**、参照位置を共有するという言い方のほうが適切です。

https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language

```jsx
let obj = {
  a: 1,
  b: 'abc'
}

let obj1 = obj
let obj2 = obj

delete obj.a
delete obj2.b
obj1.c = 5
obj2.d = 'efg'

console.log(obj1) // { c: 5, d: 'efg' }
console.log(obj2) // { c: 5, d: 'efg' }
console.log(obj) // { c: 5, d: 'efg' }
```
![passbyvalue1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/ef993316-6da4-6de8-2c83-e46bdba759dc.png)
上のように`obj1`と`obj2`は、`obj`に保存している値（Ref: 0x01、つまり参照先の位置）をコピーして自分のところに入れただけなので、`obj`か`obj1`か`obj2`のどちらからか同じ参照先の中身の変更がすべて自身へ返ります。

でも、もし`obj`がほかの参照先へ変更したら`obj1`、`obj2`も一緒に変わるのでしょうか？
```jsx
obj = {
  f: function () { }
}

console.log(obj1) // { c: 5, d: 'efg' }
console.log(obj2) // { c: 5, d: 'efg' }
console.log(obj) // { f: [Function: f] }
```
![passbyvalue2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/b9910b14-5650-c0e5-ae09-5f45453db670.png)
そもそも`obj1`も`obj2`も`obj`という変数を保存するのではなく、`obj`の値（参照位置）をコピーして保存したわけだから、別の値を受け入れた`obj`はもとの参照位置から離脱し、`obj1`、`obj2`に影響しません。

同じ参照位置がほしいなら、reassignするしかありません。
```jsx
obj2 = obj
console.log(obj2) // { f: [Function: f] }
```
![passbyvalue3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2477698/5c73bc27-6b77-e2cf-c0b8-3ce2ecd439f9.png)

上の説明のように**Shallow copy**は、同じ参照位置を値として別の変数に付与、参照位置をシェアしたわけだから、どちらからでも中身に操作できるけど、どちらも変化をうけます。
それに対して**Deep copyは中身を完全に複製し、新しい参照位置で保存するということです。**



# Method
このメソッドの作成は、[JavaScriptのinheritanceについて part2](https://qiita.com/hu-yu/items/68c6eba22014b08d2d9d#deep-copy)でデモした関数を参考しました。
```jsx
const add = function (a, b, c) {
  this.a = a
  this.b = b
  this.c = c
  this.total = function () {
    return this.a + this.b + this.c
  }
}

const greet = function (something) {
  this.name = something

  this.greeting = function () {
    this.a = '123'
    return `Hello ${this.name}`
  }
}

const obj = {
  a: 1,
  b: '2',
  c: [0, 1, 2],
  add: add,
  innerMethod: {
    greet: greet,
    g: [3, 4, 5]
  }
}
```
今回デモするコードは、`obj`のようにプリミティブ、配列、関数、そして関数と配列の入ったオブジェクトがぐちゃぐちゃになった状態を想定しています。

普通のオブジェクト（ネスト無し）なら`Object.assign()`でちゃちゃっとすぐできるのですが、ネストした状態のオブジェクトは内部が**Shallow copy**になるので、やっぱり自分で考えて作りましょうと。

そして、関数の設定は一般の関数ではなくコンストラクタ関数です。
どこのスコープでも通用する関数ならオブジェクト内部ではなくグローバルに置くのが合理的ではないかと、コンストラクタ関数にしました。



```jsx
function copyObject(obj) {
  let copy = {}
  const keys = Object.keys(obj)
  const objLength = keys.length

  for (let i = 0; i < objLength; i++) {

    if (Array.isArray(obj[keys[i]])) {

      copy[keys[i]] = [...obj[keys[i]]]

    } else if (typeof obj[keys[i]] === 'function') {

      copy[keys[i]] = new obj[keys[i]]

    } else if (typeof obj[keys[i]] === 'object' && obj[keys[i]] !== 'null') {

      copy[keys[i]] = copyObject(obj[keys[i]])

    } else {

      copy[keys[i]] = obj[keys[i]]

    }
  }
  return copy
}
```
`Object.keys()`はオブジェクトのkeyを摘出し、配列で返すメソッドです。オブジェクトは必ず`property key: property value`配置だから、keyの摘出から長さも分かったうえ、forループで走査します。
配列ならDestructuring assignment（分割代入）、関数なら`new`でインスタンスを創る、オブジェクトなら再帰処理させる。オブジェクトの条件は三番目に置いたから配列の判定と被らない。

以下は検証コードです。
```jsx
const copies = copyObject(obj)
console.log(copies)
// {
//   a: 1,
//   b: '2',
//   c: [ 0, 1, 2 ],
//   add: add {
//     a: undefined,
//     b: undefined,
//     c: undefined,
//     total: [Function (anonymous)]
//   },
//   innerMethod: {
//     greet: greet {
//       name: undefined,
//       greeting: [Function (anonymous)]
//     },
//     g: [ 3, 4, 5 ]
//   }
```
`copies.add`も`copies.innerMethod.greet`もインスタンスだけど、値が付与されないままインスタンス化したから、返した`this`オブジェクトの値は`undefined`ですね。

`copies.a`と`copies.b`はプリミティブだからスキップ、少し`copies.c`の配列を弄ってみたら
```jsx
copies.c.push(3)

console.log(copies.c) // [ 0, 1, 2, 3 ]
console.log(obj.c) // [ 0, 1, 2, 3 ]
```
大丈夫そうです。

次は`copies.add`、
```jsx
copies.add.a = 1
copies.add.b = 2
copies.add.c = 3
console.log(copies.add.total()) // 6
console.log(copies.add instanceof obj.add) // true
console.log(copies.add)
// add { a: 1, b: 2, c: 3, total: [Function (anonymous)] }
```
`this`と`new`のおかげで`copies.add`はオブジェクトになったので`copies.add()`はできないけど、内部の`copies.add.total()`関数がちゃんと動けます。

そして肝心な`copies.innerMethod`オブジェクトはどうなったのでしょう。
```jsx
copies.innerMethod.greet.name = 'Amy'
console.log(copies.innerMethod.greet.greeting()) // Hello Amy
console.log(copies.innerMethod.greet instanceof obj.innerMethod.greet) // true
console.log(copies.innerMethod.greet)
// greet {
//   name: 'Amy',
//   greeting: [Function (anonymous)],
//   a: '123'
// }
```

うまくいったみたいですね。↓は`obj`のログです。
```jsx
console.log(obj)
// {
//   a: 1,
//   b: '2',
//   c: [ 0, 1, 2 ],
//   add: [Function: add],
//   innerMethod: { greet: [Function: greet], g: [ 3, 4, 5 ] }
// }
```

ここからはちょっと気になるところも検証してみました。
まずは`copies.innerMethod.greet.greeting`に新しい関数をreassign。
```jsx
copies.innerMethod.greet.greeting = function () {
  this.a = '456'
  return `Hello ${this.name}`
}
console.log(copies.innerMethod.greet.greeting) // [Function (anonymous)]
```
ログしても中身が分からないですね。
インスタンス創ってみたら、
```jsx
const newGreeting = new copies.innerMethod.greet.greeting()

console.log(newGreeting instanceof copies.innerMethod.greet.greeting) // true
console.log(newGreeting) // { a: '456' }
console.log(newGreeting.a) // 456
```
インスタンスの内部にある関数でもインスタンスにでき、新しい`this`オブジェクトを返してくれました。
`new`インスタンスは、ある意味で複製された関数をオブジェクト化にして、`this`を通してオブジェクトの中身を構成していくのではないかと。

# Update
最近の参考文章のなかからとても簡潔な書き方を発見したので、ここに一緒にまとめようと思います。

https://jsprimer.net/

https://jsprimer.net/basic/object/#copy

元の例では配列のディープコピーはオブジェクトになってしまうので少し手を加えて変更しました。
```jsx
const shallowClone = (obj) => {
  const type = Object.prototype.toString
  if (type.call(obj).includes('Array')) {
    return [...obj]
  }
  return Object.assign({}, obj);
};

function deepClone(obj) {
  const newObj = shallowClone(obj);

  Object.keys(newObj)
    .filter((key) => typeof newObj[key] === 'object')
    .forEach((key) => {
      newObj[key] = deepClone(newObj[key])
    });
  return newObj;
}

const obj = {
  level: 1,
  nest: {
    level: 2,
    arr: ['a', 'b', 'c'],
    myMethod() {
      console.log('my method')
    }
  }
};
const cloneObj = deepClone(obj);
console.log(cloneObj);
// {
//   level: 1,
//   nest: { level: 2, arr: [ 'a', 'b', 'c' ], myMethod: [Function: myMethod] }
// }
console.log(cloneObj.nest === obj.nest); // false
```