LoginSignup
11
7

More than 5 years have passed since last update.

JavaScript: Array,Map,Object,Setをディープコピーする関数

Last updated at Posted at 2018-03-23

2018.11.05 若干変更して別記事にまとめました。

2018.10.24追記

これでいいみたい。ついでにSetも足してみました。

const mapForMap = f => a => new Map( [...a].map( f ) );  
const mapForSet = f => a => new Set( [...a].map( f ) );
const mapFromEntriesIntoObj = f => xs =>
  xs.reduce((acc, e)=>( { ...acc, [e[0]]:f(e[1]) } ), {} );
const mapForObj = f => a =>
  mapFromEntriesIntoObj( f )( Object.entries( a ) );
const getType = a => Object.prototype.toString.call(a).slice(8,-1) ;

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

以下、過去記事

完璧に何にでも対応できるディープコピーはやっぱり無理なようで(あったら教えてください)。
で、自分が困らない程度に作ってみました。

  • Map,Object用のmap関数を作る。
  • 引数が何なのか、文字列で取得。
  • 引数がArray,Map,Objectなら要素に再帰的にcloneを適用
  • それ以外ならそのまま返す
const clone = a => {
  const mapForMap = f => a => {
      const b = new Map();
      [...a.keys()].forEach( e => b.set( e, f(a.get(e)) ) );
      return b;
  }
  const mapForObj = f => a => {
      const b = {};
      Object.keys(a).forEach( e => b[e] = f(a[e]) );
      return b;
  }
  const type = Object.prototype.toString.call(a).slice(8,-1) ;
  if(type === "Array") return a.map( clone );
  if(type === "Map") return mapForMap( clone )( a );
  if(type === "Object") return mapForObj( clone )( a );
  return a;
}

class myClass{ 
  constructor(name){ 
    this.name=name 
  } 
}

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") 
   },
   j: new Map([ [ 0, 0 ], [ 1, 1 ] ]) ,
   k: new myClass("Q")
}

const cloned = clone(object)

console.log(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' } },
  j: Map { 0 => 0, 1 => 1 },
  k: { name: 'Q' } 
}
*/

だいたいは思ったとおりです。使えそう。
自前で定義したクラスmyClassはObjectに変わってしまっています。

Mapを特定したいので.toStringメソッドを使っていますが、これもやろうと思えば簡単に上書きできるので、そういうことがありうる環境では使えないかもです。

参考記事:【JavaScript】オブジェクトをDeepCopyするclone関数を書いた
ES6のObject.assignがシャローコピーなのでディープコピーする方法を考える

11
7
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
7