web-dev-qa-db-ja.com

スプレッド構文を使用したES6のディープコピー

Reduxプロジェクトのディープコピーマップメソッドを作成しようとしていますが、これは配列ではなくオブジェクトで動作します。 Reduxでは、以前の状態では各状態が何も変わらないはずだと読みました。

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    output[key] = callback.call(this, {...object[key]});

    return output;
    }, {});
}

できます:

    return mapCopy(state, e => {

            if (e.id === action.id) {
                 e.title = 'new item';
            }

            return e;
        })

ただし、内部アイテムはディープコピーされないため、次のように微調整する必要があります。

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    let newObject = {...object[key]};
    newObject.style = {...newObject.style};
    newObject.data = {...newObject.data};

    output[key] = callback.call(this, newObject);

    return output;
    }, {});
}

どのオブジェクトが渡されるかを知る必要があるため、これはエレガントではありません。 ES6でスプレッド構文を使用してオブジェクトをディープコピーする方法はありますか?

64
Guy

そのような機能はES6に組み込まれていません。やりたいことに応じて、いくつかのオプションがあると思います。

本当にディープコピーしたい場合:

  1. ライブラリを使用します。たとえば、lodashには cloneDeep メソッドがあります。
  2. 独自のクローン作成機能を実装します。

特定の問題の代替ソリューション(ディープコピーなし)

ただし、いくつかの点を変更する場合は、作業を節約できます。私はあなたがあなたの機能へのすべての呼び出しサイトを制御すると仮定しています。

  1. mapCopyに渡されるすべてのコールバックは、既存のオブジェクトを変更するのではなく、新しいオブジェクトを返す必要があることを指定します。例えば:

    mapCopy(state, e => {
      if (e.id === action.id) {
        return Object.assign({}, e, {
          title: 'new item'
        });
      } else {  
        return e;
      }
    });
    

    これは Object.assign を使用して新しいオブジェクトを作成し、その新しいオブジェクトにeのプロパティを設定してから、その新しいオブジェクトに新しいタイトルを設定します。つまり、既存のオブジェクトを変更することはなく、必要な場合にのみ新しいオブジェクトを作成します。

  2. mapCopyが本当に簡単になりました:

    export const mapCopy = (object, callback) => {
      return Object.keys(object).reduce(function (output, key) {
        output[key] = callback.call(this, object[key]);
        return output;
      }, {});
    }
    

基本的に、mapCopyは呼び出し元が正しいことをすることを信頼しています。これが、あなたがすべての通話サイトを管理していると仮定した理由です。

51
Frank Tan

代わりにこれをディープコピーに使用します

var newObject = JSON.parse(JSON.stringify(oldObject))
var oldObject = {
  name: 'A',
  address: {
    street: 'Station Road',
    city: 'Pune'
  }
}
var newObject = JSON.parse(JSON.stringify(oldObject));

newObject.address.city = 'Delhi';
console.log('newObject');
console.log(newObject);
console.log('oldObject');
console.log(oldObject);
67
Nikhil Mahirrao

MDNから

注:Spread構文は、配列のコピー中に事実上1レベル深くなります。したがって、次の例が示すように、多次元配列のコピーには適さない可能性があります(Object.assign()およびspread構文でも同じです)。

個人的には、マルチレベルのオブジェクト/配列のクローン作成に LodashのcloneDeep 関数を使用することをお勧めします。

これが実際の例です:

const arr1 = [{ 'a': 1 }];

const arr2 = [...arr1];

const arr3 = _.clone(arr1);

const arr4 = arr1.slice();

const arr5 = _.cloneDeep(arr1);

const arr6 = [...{...arr1}]; // a bit ugly syntax but it is working!


// first level
console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // false
console.log(arr1 === arr4); // false
console.log(arr1 === arr5); // false
console.log(arr1 === arr6); // false

// second level
console.log(arr1[0] === arr2[0]); // true
console.log(arr1[0] === arr3[0]); // true
console.log(arr1[0] === arr4[0]); // true
console.log(arr1[0] === arr5[0]); // false
console.log(arr1[0] === arr6[0]); // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
19
Mina Luke

私はこれを頻繁に使用します:

function deepCopy(obj) {
    if(typeof obj !== 'object' || obj === null) {
        return obj;
    }

    if(obj instanceof Date) {
        return new Date(obj.getTime());
    }

    if(obj instanceof Array) {
        return obj.reduce((arr, item, i) => {
            arr[i] = deepCopy(item);
            return arr;
        }, []);
    }

    if(obj instanceof Object) {
        return Object.keys(obj).reduce((newObj, key) => {
            newObj[key] = deepCopy(obj[key]);
            return newObj;
        }, {})
    }
}
6
HectorGuo
function deepclone(obj) {
    let newObj = {};

    if (typeof obj === 'object') {
        for (let key in obj) {
            let property = obj[key],
                type = typeof property;
            switch (type) {
                case 'object':
                    if( Object.prototype.toString.call( property ) === '[object Array]' ) {
                        newObj[key] = [];
                        for (let item of property) {
                            newObj[key].Push(this.deepclone(item))
                        }
                    } else {
                        newObj[key] = deepclone(property);
                    }
                    break;
                default:
                    newObj[key] = property;
                    break;

            }
        }
        return newObj
    } else {
        return obj;
    }
}
1
Jeroen Breen
// use: clone( <thing to copy> ) returns <new copy>
// untested use at own risk
function clone(o, m){
  // return non object values
  if('object' !==typeof o) return o
  // m: a map of old refs to new object refs to stop recursion
  if('object' !==typeof m || null ===m) m =new WeakMap()
  var n =m.get(o)
  if('undefined' !==typeof n) return n
  // shallow/leaf clone object
  var c =Object.getPrototypeOf(o).constructor
  // TODO: specialize copies for expected built in types i.e. Date etc
  switch(c) {
    // shouldn't be copied, keep reference
    case Boolean:
    case Error:
    case Function:
    case Number:
    case Promise:
    case String:
    case Symbol:
    case WeakMap:
    case WeakSet:
      n =o
      break;
    // array like/collection objects
    case Array:
      m.set(o, n =o.slice(0))
      // recursive copy for child objects
      n.forEach(function(v,i){
        if('object' ===typeof v) n[i] =clone(v, m)
      });
      break;
    case ArrayBuffer:
      m.set(o, n =o.slice(0))
      break;
    case DataView:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.byteLength))
      break;
    case Map:
    case Set:
      m.set(o, n =new (c)(clone(Array.from(o.entries()), m)))
      break;
    case Int8Array:
    case Uint8Array:
    case Uint8ClampedArray:
    case Int16Array:
    case Uint16Array:
    case Int32Array:
    case Uint32Array:
    case Float32Array:
    case Float64Array:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.length))
      break;
    // use built in copy constructor
    case Date:
    case RegExp:
      m.set(o, n =new (c)(o))
      break;
    // fallback generic object copy
    default:
      m.set(o, n =Object.assign(new (c)(), o))
      // recursive copy for child objects
      for(c in n) if('object' ===typeof n[c]) n[c] =clone(n[c], m)
  }
  return n
}
0
user10919042

JavaScriptでオブジェクトを機能的にディープコピーする方法を探していました。見つからなかったので作りました。このアルゴリズムは不変であり、副作用は発生せず、命令型ではなく宣言型です。楽しい!

const deepCopy = obj => (Array.isArray(obj) ? Object.values : obj=>obj)(Object.keys(obj).reduce((acc, key) => 
  ({...acc, [key]: (
    !obj[key] ? obj[key]
    : typeof obj[key] === 'object' ? deepCopy(obj[key])
    : obj[key]
  )}),
  {}
))

基本的には、オブジェクトのキーが別のオブジェクトに削減されています。ネストされたオブジェクトと配列を再帰的に処理し、配列をコピーする前にキーとして使用される数値インデックスを持つオブジェクトに変換します。ネストされた配列をオブジェクトに型変換して戻すには、Object.valuesを使用します。

上記の関数は、次の一連の操作を実行します。

  1. コピーするオブジェクトが配列の場合、1a)解決された値をObject.values()に渡します。それ以外の場合、2b)解決されたオブジェクトを返すように関数を設定します。
  2. コピーするオブジェクトのキーから配列を作成し、
  3. キー配列をreduce関数にフィードし、
  4. 指定されたキーで新しいオブジェクトの値を設定/割り当てます:4a)値が偽である場合、設定します。 4b)値がオブジェクト(暗黙的に配列を含む)の場合、値をdeepCopyしてから割り当てます。 4c)それ以外の場合は、設定します。
0
Jacob Penney