Copia profonda in ES6 utilizzando la sintassi diffusa


99

Sto cercando di creare un metodo di mappa di copia profonda per il mio progetto Redux che funzionerà con gli oggetti piuttosto che con gli array. Ho letto che in Redux ogni stato non dovrebbe cambiare nulla negli stati precedenti.

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

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

    return output;
    }, {});
}

Funziona:

    return mapCopy(state, e => {

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

            return e;
        })

Tuttavia non copia in profondità gli elementi interni, quindi devo modificarlo per:

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;
    }, {});
}

Questo è meno elegante in quanto richiede di sapere quali oggetti vengono passati. C'è un modo in ES6 per utilizzare la sintassi di diffusione per copiare in profondità un oggetto?



8
Questo è un problema XY. Non dovresti lavorare molto sulle proprietà profonde in Redux. invece dovresti semplicemente creare un altro riduttore che funzioni sulla fetta figlio della forma dello stato e quindi usarlo combineReducersper comporre i due (o più) insieme. Se usi tecniche di redux idiomatiche, il tuo problema di clonazione profonda degli oggetti scompare.
Grazie

Risposte:


72

Nessuna funzionalità di questo tipo è incorporata in ES6. Penso che tu abbia un paio di opzioni a seconda di cosa vuoi fare.

Se vuoi davvero copiare in profondità:

  1. Usa una libreria. Ad esempio, lodash ha un cloneDeepmetodo.
  2. Implementa la tua funzione di clonazione.

Soluzione alternativa al tuo problema specifico (nessuna copia approfondita)

Tuttavia, penso che se sei disposto a cambiare un paio di cose, puoi risparmiare un po 'di lavoro. Presumo che controlli tutti i siti di chiamata alla tua funzione.

  1. Specificare che tutti i callback passati a mapCopydevono restituire nuovi oggetti invece di modificare l'oggetto esistente. Per esempio:

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

    Questo utilizza Object.assignper creare un nuovo oggetto, imposta le proprietà di esu quel nuovo oggetto, quindi imposta un nuovo titolo su quel nuovo oggetto. Ciò significa che non muti mai oggetti esistenti e ne crei di nuovi solo quando necessario.

  2. mapCopy può essere davvero semplice ora:

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

In sostanza, mapCopyè fidarsi dei suoi chiamanti per fare la cosa giusta. Questo è il motivo per cui ho detto che questo presuppone che tu controlli tutti i siti di chiamata.


3
Object.assign non esegue la copia completa degli oggetti. vedere developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Object.assign () copia i valori delle proprietà. "Se il valore di origine è un riferimento a un oggetto, copia solo quel valore di riferimento."
Greg Somers

Destra. Questa è una soluzione alternativa che non implica la copia profonda. Aggiornerò la mia risposta per essere più esplicito al riguardo.
Frank Tan

102

Usalo invece per la copia completa

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);


63
Funziona solo se non è necessario clonare le funzioni. JSON ignorerà tutte le funzioni in modo da non averle nel clone.
Noland

7
A parte le funzioni, avrai problemi con undefined e null utilizzando questo metodo
James Heazlewood

2
Avrai anche problemi con le classi definite dall'utente, poiché le catene di prototipi non sono serializzate.
Patrick Roberts

8
La tua soluzione che utilizza la serializzazione JSON presenta alcuni problemi. In questo modo perderai qualsiasi proprietà Javascript che non ha un tipo equivalente in JSON, come Function o Infinity. Qualsiasi proprietà assegnata a undefined verrà ignorata da JSON.stringify, in modo che non vengano rilevate sull'oggetto clonato. Inoltre, alcuni oggetti vengono convertiti in stringhe, come Date, Set, Map e molti altri.
Jonathan Brizio

2
Stavo avendo un terribile incubo nel cercare di creare una copia fedele di una serie di oggetti - oggetti che erano essenzialmente valori di dati, nessuna funzione. Se questo è tutto ciò di cui devi preoccuparti, allora questo approccio funziona magnificamente.
Charlie,

29

Da MDN

Nota: la sintassi di diffusione va effettivamente in profondità di un livello durante la copia di un array. Pertanto, potrebbe non essere adatto per la copia di array multidimensionali come mostra il seguente esempio (è lo stesso con Object.assign () e spread syntax).

Personalmente, suggerisco di utilizzare la funzione cloneDeep di Lodash per la clonazione di oggetti / array multi-livello.

Ecco un esempio funzionante:

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>


4
arr6 non funziona per me. Nel browser (chrome 59.0 che supporta ES6 ottengo Uncaught SyntaxError: Unexpected token ... e nel nodo 8.9.3 che supporta ES7 ottengo TypeError: undefined non è una funzione repl:
Achi Even-dar

@ AchiEven-dar not sire why you got a error. Puoi eseguire questo codice direttamente in stackoverflow premendo il pulsante blu Run code snippete dovrebbe funzionare correttamente.
Mina Luke

3
arr6 non funziona anche per me. Nel browser - chrome 65
yehonatan yehezkel

17

Lo uso spesso:

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;
        }, {})
    }
}

3
const a = {
  foods: {
    dinner: 'Pasta'
  }
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

Usare JSON.stringifyed JSON.parseè il modo migliore. Perché utilizzando l'operatore spread non otterremo la risposta efficiente quando l'oggetto json contiene un altro oggetto al suo interno. dobbiamo specificarlo manualmente.


1
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
// 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
}

I commenti sono nel codice per chi cerca una spiegazione.
Wookies-Will il

1
const cloneData = (dataArray) => {
    newData= []
    dataArray.forEach((value) => {
        newData.push({...value})
    })
    return newData
}
  • a = [{name: "siva"}, {name: "siva1"}];
  • b = miaCopia (a)
  • b === a // false`

1

Io stesso sono arrivato a queste risposte lo scorso giorno, cercando di trovare un modo per copiare in profondità strutture complesse, che possono includere collegamenti ricorsivi. Poiché non ero soddisfatto di nulla che fosse stato suggerito prima, ho implementato questa ruota da solo. E funziona abbastanza bene. Spero che aiuti qualcuno.

Utilizzo di esempio:

OriginalStruct.deep_copy = deep_copy; // attach the function as a method

TheClone = OriginalStruct.deep_copy();

Si prega di guardare https://github.com/latitov/JS_DeepCopy per esempi dal vivo su come usarlo, e anche deep_print () è lì.

Se ne hai bisogno velocemente, ecco la fonte della funzione deep_copy ():

function deep_copy() {
    'use strict';   // required for undef test of 'this' below

    // Copyright (c) 2019, Leonid Titov, Mentions Highly Appreciated.

    var id_cnt = 1;
    var all_old_objects = {};
    var all_new_objects = {};
    var root_obj = this;

    if (root_obj === undefined) {
        console.log(`deep_copy() error: wrong call context`);
        return;
    }

    var new_obj = copy_obj(root_obj);

    for (var id in all_old_objects) {
        delete all_old_objects[id].__temp_id;
    }

    return new_obj;
    //

    function copy_obj(o) {
        var new_obj = {};
        if (o.__temp_id === undefined) {
            o.__temp_id = id_cnt;
            all_old_objects[id_cnt] = o;
            all_new_objects[id_cnt] = new_obj;
            id_cnt ++;

            for (var prop in o) {
                if (o[prop] instanceof Array) {
                    new_obj[prop] = copy_array(o[prop]);
                }
                else if (o[prop] instanceof Object) {
                    new_obj[prop] = copy_obj(o[prop]);
                }
                else if (prop === '__temp_id') {
                    continue;
                }
                else {
                    new_obj[prop] = o[prop];
                }
            }
        }
        else {
            new_obj = all_new_objects[o.__temp_id];
        }
        return new_obj;
    }
    function copy_array(a) {
        var new_array = [];
        if (a.__temp_id === undefined) {
            a.__temp_id = id_cnt;
            all_old_objects[id_cnt] = a;
            all_new_objects[id_cnt] = new_array;
            id_cnt ++;

            a.forEach((v,i) => {
                if (v instanceof Array) {
                    new_array[i] = copy_array(v);
                }
                else if (v instanceof Object) {
                    new_array[i] = copy_object(v);
                }
                else {
                    new_array[i] = v;
                }
            });
        }
        else {
            new_array = all_new_objects[a.__temp_id];
        }
        return new_array;
    }
}

Saluti@!


1

Ecco il mio algoritmo di copia profonda.

const DeepClone = (obj) => {
     if(obj===null||typeof(obj)!=='object')return null;
    let newObj = { ...obj };

    for (let prop in obj) {
      if (
        typeof obj[prop] === "object" ||
        typeof obj[prop] === "function"
      ) {
        newObj[prop] = DeepClone(obj[prop]);
      }
    }

    return newObj;
  };

Devi anche controllare se 'obj [prop]! == null' come typeof (null) restituisce anche 'oggetto'
Pramod Mali

0

Ecco la funzione deepClone che gestisce tutti i tipi di dati primitivi, array, oggetti e funzioni

function deepClone(obj){
	if(Array.isArray(obj)){
		var arr = [];
		for (var i = 0; i < obj.length; i++) {
			arr[i] = deepClone(obj[i]);
		}
		return arr;
	}

	if(typeof(obj) == "object"){
		var cloned = {};
		for(let key in obj){
			cloned[key] = deepClone(obj[key])
		}
		return cloned;	
	}
	return obj;
}

console.log( deepClone(1) )

console.log( deepClone('abc') )

console.log( deepClone([1,2]) )

console.log( deepClone({a: 'abc', b: 'def'}) )

console.log( deepClone({
  a: 'a',
  num: 123,
  func: function(){'hello'},
  arr: [[1,2,3,[4,5]], 'def'],
  obj: {
    one: {
      two: {
        three: 3
      }
    }
  }
}) ) 

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.