JavaScript:Array,Object,Map,Set,Dateをまとめてディープコピーする

[以前の記事](https://qiita.com/ttatsf/items/7f2891d0b78ba36c2dca)の続きです。
あれからいろいろ考えて、今はこれがいいかな?と思っています。

引数の中味をあまり気にしないでディープコピーできる関数cloneです。
(さんざんディープコピーって言ってるのに関数名がcloneって...ディープコピー = クローンと読み替えるかしてお読みください。)

```js:
const mapForMap = f => a => 
  new Map( [...a].map( ([key, value]) =>[key, f(value)] ) );  
const mapForSet = f => a => new Set( [...a].map( f ) );
const entriesMapIntoObj = f => xs => 
  xs.reduce(
    (acc, [key, value]) => ({ ...acc, [key]:f(value) })
    , {} 
  );
const mapForObj = f => a =>
  entriesMapIntoObj( f )( Object.entries( a ) );
const getType = a => Object.prototype.toString.call(a);

const clone = a => {
  const type = getType( a );
  return (type === "[object Array]")?  a.map( clone )
        :(type === "[object Map]")?    mapForMap( clone )( a )
        :(type === "[object Set]")?    mapForSet( clone )( a )
        :(type === "[object Object]")? mapForObj( clone )( a )
        :(type === "[object Date]")?   new Date( a )
        : a;
}
```


```js:
//使用例：
const object = { 
   a: 1, 
   b: 'a', 
   c: '', 
   d: null, 
   e: undefined, 
   f: true, 
   g: [1, 2, 3], 
   h: function () { console.log('h'); }, 
   i: { 
     a: 1, 
     b: 'a', 
     c: '', 
     d: null, 
     e: undefined, 
     f: true, 
     g: [1, 2, 3], 
     h: function () { console.log('h'); }, 
     i: { a: 1 }, 
     j: new Map([ [ 0, 0 ], [ 1, 1 ] ]) ,
     k: new myClass("Q"),
     l: new Set( [ 1, 'string', {1:1,2:2} ] ),
     m: new Date()
   },
   j: new Map([ [ 0, 0 ], [ 1, 1 ] ]) ,
   k: new myClass("Q"),
   l: new Set( [ 1, 'string', {1:1,2:2} ] ),
   m: new Date()
}

const cloned = clone(object);

for(const e in object) object[e] = 0; // Objectのすべての要素の値を0にする
object
//=>
{ a: 0, b: 0, c: 0, d: 0, e: 0, f: 0, g: 0, h: 0, i: 0, j: 0, k: 0, l: 0, m: 0 }

cloned
//=>ディープコピーされているので、objectが変更されてもclonedは変わらない
{ a: 1,
  b: 'a',
  c: '',
  d: null,
  e: undefined,
  f: true,
  g: [ 1, 2, 3 ],
  h: [Function: h],
  i: 
   { a: 1,
     b: 'a',
     c: '',
     d: null,
     e: undefined,
     f: true,
     g: [ 1, 2, 3 ],
     h: [Function: h],
     i: { a: 1 },
     j: Map { 0 => 0, 1 => 1 },
     k: { name: 'Q' },
     l: Set { 1, 'string', [Object] },
     m: 2018-11-04T16:57:38.609Z },
  j: Map { 0 => 0, 1 => 1 },
  k: { name: 'Q' },
  l: Set { 1, 'string', { 1: 1, 2: 2 } },
  m: 2018-11-04T16:57:38.609Z }
```
階層が深いところの表示が[Object]に省略されていますが、

```js:
console.log(cloned.i.l);
//=> Set { 1, 'string', { 1: 1, 2: 2 } }
```
とちゃんとコピーされています。

# どうなっている?

一応、予定している入力は、Array、Map、Set、Object、Dateとプリミティブ(数、文字列、真偽値、undefined、null)と関数です。これだけあれば十分でしょ?

仕組み自体は簡単です。

+ 引数 a の型を取得して
+ それぞれの型専用のディープコピー関数を適用する

これだけ。コードにすると：

```js:
const clone = a => {
  const type = getType( a );
  return (type === "[object Array]")?  cloneArray( a )
        :(type === "[object Map]")?    cloneMap( a )
        :(type === "[object Set]")?    cloneSet( a )
        :(type === "[object Object]")? cloneObj( a )
        :(type === "[object Date]")?   cloneDate( a )
        : cloneOthers( a );
}
```
それっぽく名前をつけただけなので、それぞれの型専用のディープコピー関数を作っていきます。
まずは簡単なところから。

## cloneOthers
ここにはArrayでもMapでもSetでもObjectでもDateでもないものが来ます。
プリミティブ(数、文字列、真偽値、undefined、null)と、関数もここに入ります。
これらはそのまま返されます。

```js:
const cloneOthers = a => a;
```
今のところ、予定していない何かが来た場合もここでそのまま返されます。
実用上問題ないのでそうしていますが、問題があれば要改造(その何か専用のclone関数を作る)です。

## cloneDate
a を元に新しいDateオブジェクトを作ります。

```js:
const cloneDate = a => new Date( a );
```
## Array,Map,Set,Objectの場合
内部のそれぞれの要素をcloneで再帰的にディープコピーします。
配列にはmapが使えますが、他には直接は使えません。
それぞれ、map的なことができる関数を定義します。

```js:
const cloneArray = a => a.map( clone );
const cloneMap = a => mapForMap( clone )( a );
const cloneSet = a => mapForSet( clone )( a );
const cloneObj = a => mapForObj( clone )( a )
```
### mapForMap,mapForSet
Map、Setはスプレッド構文で一旦配列にしてmapすることができます。
mapした配列を元に新しいMap,Setを作ります。

```js:
//[...a]で[key, value]の配列にし各要素のvalueにfを適用した配列を元に、新しいMapを作る
const mapForMap = f => a => 
  new Map( [...a].map( ([key, value]) =>[key, f(value)] ) );  

const mapForSet = f => a => new Set( [...a].map( f ) );
```
### mapForObj
Objectは、entries()で[key, value]の配列に変換して、reduceでvalueにfを適用しつつ、空のObjectに要素を加えていき、新しいObjectを作ります。

```js:
// [key, value]の配列 xs の各要素の value に f を適用してObjectにする
const entriesMapIntoObj = f => xs => 
  xs.reduce(
    (acc, [key, value]) => ({ ...acc, [key]:f(value) })
    , {} 
  );
//aを[key, value]の配列に変換して、entriesMapIntoObj( f )を適用する
const mapForObj = f => a =>
  entriesMapIntoObj( f )( Object.entries( a ) );
```
※ fromEntries()が現時点であまり実装されてないようなのでこんなことしてます。

## getType
Array,Map,Set Objectを区別するにはObject.prototype.toString.call()を使うそうです。

```js:
const getType = a => Object.prototype.toString.call(a);
```
返ってきた文字列で分岐します。

これで全部の部品が揃いました。 組合せると冒頭のコードになります。

何か勘違い、間違い、もっといい方法等あればコメントよろしくです。