Deep Copy in ES6 unter Verwendung der Spread-Syntax

97

Ich versuche, eine Deep Copy Map-Methode für mein Redux-Projekt zu erstellen, die eher mit Objekten als mit Arrays funktioniert. Ich habe gelesen, dass in Redux jeder Zustand nichts an den vorherigen Zuständen ändern sollte.

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

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

    return output;
    }, {});
}

Es klappt:

    return mapCopy(state, e => {

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

            return e;
        })

Da innere Elemente jedoch nicht tief kopiert werden, muss ich Folgendes anpassen:

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

Dies ist weniger elegant, da bekannt sein muss, welche Objekte übergeben werden. Gibt es in ES6 eine Möglichkeit, die Spread-Syntax zum tiefen Kopieren eines Objekts zu verwenden?

Kerl
quelle
8
Dies ist ein XY-Problem. Sie sollten nicht viel an tiefen Eigenschaften in Redux arbeiten müssen. Stattdessen sollten Sie einfach einen weiteren Reduzierer erstellen, der auf dem untergeordneten Slice der Statusform funktioniert, und dann combineReducersdie beiden (oder mehr) zusammensetzen. Wenn Sie idiomatische Redux-Techniken verwenden, verschwindet Ihr Problem des tiefen Klonens von Objekten.
Danke

Antworten:

71

In ES6 ist keine solche Funktionalität integriert. Ich denke, Sie haben ein paar Möglichkeiten, je nachdem, was Sie tun möchten.

Wenn Sie wirklich tief kopieren möchten:

  1. Verwenden Sie eine Bibliothek. Zum Beispiel hat lodash einecloneDeep Methode.
  2. Implementieren Sie Ihre eigene Klonfunktion.

Alternative Lösung für Ihr spezifisches Problem (keine tiefe Kopie)

Ich denke jedoch, wenn Sie bereit sind, ein paar Dinge zu ändern, können Sie sich etwas Arbeit sparen. Ich gehe davon aus, dass Sie alle Anrufstellen für Ihre Funktion steuern.

  1. Geben Sie an, dass alle an übergebenen Rückrufe mapCopyneue Objekte zurückgeben müssen, anstatt das vorhandene Objekt zu mutieren. Beispielsweise:

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

    Dies wird verwendet Object.assign, um ein neues Objekt zu erstellen, Eigenschaften für edieses neue Objekt festzulegen und dann einen neuen Titel für dieses neue Objekt festzulegen. Dies bedeutet, dass Sie vorhandene Objekte niemals mutieren und nur bei Bedarf neue erstellen.

  2. mapCopy kann jetzt ganz einfach sein:

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

Vertraut im Wesentlichen darauf, dass mapCopyseine Anrufer das Richtige tun. Aus diesem Grund habe ich vorausgesetzt, dass Sie alle Anrufstellen kontrollieren.

Frank Tan
quelle
3
Object.assign kopiert Objekte nicht tief. Siehe developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Object.assign () kopiert Eigenschaftswerte. "Wenn der Quellwert eine Referenz auf ein Objekt ist, wird nur dieser Referenzwert kopiert."
Greg Somers
Richtig. Dies ist eine alternative Lösung, bei der kein tiefes Kopieren erforderlich ist . Ich werde meine Antwort aktualisieren, um dies genauer zu beschreiben.
Frank Tan
101

Verwenden Sie dies stattdessen für eine tiefe Kopie

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

Nikhil Mahirrao
quelle
63
Dies funktioniert nur, wenn Sie keine Funktionen klonen müssen. JSON ignoriert alle Funktionen, sodass Sie sie nicht im Klon haben.
Noland
7
Abgesehen von Funktionen haben Sie Probleme mit undefiniert und null mit dieser Methode
James Heazlewood
2
Sie haben auch Probleme mit benutzerdefinierten Klassen, da Prototypketten nicht serialisiert werden.
Patrick Roberts
8
Ihre Lösung mit JSON-Serialisierung weist einige Probleme auf. Auf diese Weise verlieren Sie alle Javascript-Eigenschaften, die in JSON keinen äquivalenten Typ haben, wie z. B. Function oder Infinity. Alle Eigenschaften, die undefined zugewiesen sind, werden von JSON.stringify ignoriert, sodass sie im geklonten Objekt übersehen werden. Außerdem werden einige Objekte in Zeichenfolgen konvertiert, z. B. Datum, Festlegen, Zuordnen und viele andere.
Jonathan Brizio
2
Ich hatte einen schrecklichen Albtraum, als ich versuchte, eine echte Kopie eines Arrays von Objekten zu erstellen - Objekte, die im Wesentlichen Datenwerte waren, keine Funktionen. Wenn das alles ist, worüber Sie sich Sorgen machen müssen, dann funktioniert dieser Ansatz wunderbar.
Charlie
29

Von MDN

Hinweis: Die Spread-Syntax geht beim Kopieren eines Arrays effektiv eine Ebene tiefer. Daher ist es möglicherweise nicht zum Kopieren mehrdimensionaler Arrays geeignet, wie das folgende Beispiel zeigt (dies gilt auch für Object.assign () und die Spread-Syntax).

Persönlich schlage ich vor, zu verwenden die cloneDeep- Funktion von Lodash für das mehrstufige Klonen von Objekten / Arrays zu verwenden.

Hier ist ein Arbeitsbeispiel:

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>

Mina Luke
quelle
4
arr6 arbeitet nicht für mich. Im Browser (Chrome 59.0, der ES6 unterstützt, erhalte ich einen nicht erfassten SyntaxError: Unerwartetes Token ... und in Knoten 8.9.3, der ES7 unterstützt, erhalte ich TypeError: undefined ist keine Funktion bei Repl: 1: 22
Achi Even-dar
@ AchiEven-dar nicht Vater, warum du einen Fehler bekommen hast. Sie können diesen Code direkt im Stackoverflow ausführen, indem Sie die blaue Taste drücken. Run code snippetEr sollte ordnungsgemäß ausgeführt werden.
Mina Luke
3
arr6 arbeitet auch nicht für mich. In Browser - Chrom 65
Yehonatan Yehezkel
17

Ich benutze dies oft:

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

Verwenden JSON.stringifyund JSON.parseist der beste Weg. Denn mit dem Spread-Operator erhalten wir keine effiziente Antwort, wenn das json-Objekt ein anderes Objekt enthält. das müssen wir manuell spezifizieren.

Shashidhar Reddy
quelle
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;
    }
}
Jeroen Breen
quelle
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
}
user10919042
quelle
Kommentare sind im Code für diejenigen, die nach Erklärungen suchen.
Wookies-Will-Code
1
const cloneData = (dataArray) => {
    newData= []
    dataArray.forEach((value) => {
        newData.push({...value})
    })
    return newData
}
  • a = [{name: "siva"}, {name: "siva1"}];
  • b = myCopy (a)
  • b === a // false`
Harish Sekar
quelle
1

Ich selbst bin letzten Tag auf diese Antworten gestoßen und habe versucht, komplexe Strukturen, die rekursive Links enthalten können, tief zu kopieren. Da ich mit den Vorschlägen noch nicht zufrieden war, habe ich dieses Rad selbst implementiert. Und es funktioniert ganz gut. Hoffe es hilft jemandem.

Anwendungsbeispiel:

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

TheClone = OriginalStruct.deep_copy();

Unter https://github.com/latitov/JS_DeepCopy finden Sie Live-Beispiele zur Verwendung. Außerdem ist deep_print () vorhanden.

Wenn Sie es schnell brauchen, finden Sie hier die Quelle der Funktion 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;
    }
}

Prost@!

latitov
quelle
1

Hier ist mein Deep-Copy-Algorithmus.

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;
  };
Бектур Муратов
quelle
Sie müssen auch überprüfen, ob 'obj [prop]! == null' als typeof (null) auch 'object'
zurückgibt
0

Hier ist die deepClone-Funktion, die alle primitiven, Array-, Objekt- und Funktionsdatentypen verarbeitet

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

Ganesh Phirke
quelle