Klonen eines Arrays in Javascript / Typescript

76

Ich habe eine Reihe von zwei Objekten:

genericItems: Item[] = [];
backupData: Item[] = [];

Ich fülle meine HTML-Tabelle mit genericItemsDaten. Die Tabelle kann geändert werden. Es gibt eine Reset-Taste, um alle vorgenommenen Änderungen rückgängig zu machen backUpData. Dieses Array wird von einem Dienst gefüllt:

getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
  result => {
     this.genericItems = result;
  });
     this.backupData = this.genericItems.slice();
  }

Meine Idee war, dass die Benutzeränderungen im ersten Array wiedergegeben werden und das zweite Array als Backup für den Reset-Vorgang verwendet werden kann. Das Problem, mit dem ich hier konfrontiert bin, ist, wenn der Benutzer die Tabelle ändert ( genericItems[])das zweite Array wird backupDataebenfalls geändert.

Wie passiert das und wie kann man das verhindern?

Arun
quelle
Sieht so aus, als hätten Sie eine flache Kopie des Arrays erstellt. Es hört sich so an, als würden Sie die Objekte ändern, die sie gehalten haben, und die Änderungen sehen. Sie müssen eine tiefe Kopie erstellen oder eine andere Art der Darstellung Ihrer Daten finden.
Jeff Mercado
Sie verweisen auf dieselbe Referenz. Wenn Sie ein neues Array mit einer Bibliothek wie lodash oder ähnlichem zurückgeben, tritt dieses Problem nicht auf.
Latin Warrior
slice()wird ein neues Objekt aus einem anderen Array erstellen, denke ich ...
Arun
Das zweite Array wird geändert, da Sie statt eines neuen Arrays lediglich auf das ursprüngliche Array verweisen. Wenn Sie Typenskript und ES6 verwenden, können Sie eine Kopie wie diese erstellen. This.backupData = [... this.genericItems] Dadurch wird eine Kopie des Arrays erstellt. Hoffentlich hilft das!
Molik Miah
2
@ MolikMiah Ich sage, das slicenimmt ein Array und kopiert jede Referenz in ein neues Array. Das alte und das neue Array unterscheiden sich also tatsächlich, aber die darin enthaltenen Objekte sind genau gleich. Also sollte es das gleiche sein wie zu tun[...array]
Frank Modica

Antworten:

158

Ein Objekt klonen:

const myClonedObject = Object.assign({}, myObject);

Klonen eines Arrays:

  • Option 1, wenn Sie ein Array primitiver Typen haben:

const myClonedArray = Object.assign([], myArray);

  • Option 2 - Wenn Sie ein Array von Objekten haben:
const myArray= [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }];
const myClonedArray = [];
myArray.forEach(val => myClonedArray.push(Object.assign({}, val)));
abahet
quelle
13
Wenn Ihr Array ein Array von Objekten ist (keine primitiven Typen), müssen Sie mit Ihrer flachen Kopie eine Ebene tiefer gehen. Für mich bestand die Lösung darin, das Array zu durchlaufen und die Objekte zu klonen. Dh const myArray= [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }]; const myClonedArray = []; myArray.map(val => myClonedArray.push(Object.assign({}, val)));. Eine alternative Lösung für eine ordnungsgemäße Tiefenkopie wäre die JSON-Serialisierung, wie in anderen Antworten erwähnt.
mumblesNZ
@mumblesNZ, wenn Sie wirklich von einer tiefen Kopie sprechen, reichen auch zwei Ebenen nicht aus. Sie müssten so etwas wie Lodashs verwenden _.cloneDeep(obj). Die JSON-Serialisierung würde funktionieren, wie Sie sagten, aber das ist ein sehr umständlicher Weg, dies zu tun.
user2683747
62

Das Klonen von Arrays und Objekten in Javascript hat eine andere Syntax . Früher oder später lernt jeder den Unterschied auf die harte Tour und landet hier.

In Typescript und ES6 können Sie den Spread-Operator für Array und Objekt verwenden:

const myClonedArray  = [...myArray];  // This is ok for [1,2,'test','bla']
                                      // But wont work for [{a:1}, {b:2}]. 
                                      // A bug will occur when you 
                                      // modify the clone and you expect the 
                                      // original not to be modified.
                                      // The solution is to do a deep copy
                                      // when you are cloning an array of objects.

Um eine tiefe Kopie eines Objekts zu erstellen, benötigen Sie eine externe Bibliothek:

import {cloneDeep} from 'lodash';
const myClonedArray = cloneDeep(myArray);     // This works for [{a:1}, {b:2}]

Der Spread-Operator arbeitet auch mit Objekten, erstellt jedoch nur eine flache Kopie (erste untergeordnete Ebene).

const myShallowClonedObject = {...myObject};   // Will do a shallow copy
                                               // and cause you an un expected bug.

Um eine tiefe Kopie eines Objekts zu erstellen, benötigen Sie eine externe Bibliothek:

 import {cloneDeep} from 'lodash';
 const deeplyClonedObject = cloneDeep(myObject);   // This works for [{a:{b:2}}]
David Dehghan
quelle
1
Ah! Der Penny fällt! Zeit, die Verwendung des Spread-Operators zum Klonen von
Objektarrays zu beenden
const myClonedArray = [... myArray] arbeitet für [{a: 1}, {b: 2}].
Suyash Gupta
2
Nein, tut es nicht. Versuchen Sie, Ihr geklontes Objekt zu ändern. Dadurch wird auch das Original geändert. Was Sie haben, ist eine Kopie als Referenz und kein Wert.
David Dehghan
Zum Deepcloning kann auch "Object.assign (Object.create (Object.getPrototypeOf (obj)), obj)" verwendet werden. anstatt eine externe Bibliothek zu verwenden.
CoderApprentice
17

Die Verwendung einer Karte oder einer ähnlichen Lösung hilft nicht dabei, ein Array von Objekten tief zu klonen. Eine einfachere Möglichkeit, dies ohne Hinzufügen einer neuen Bibliothek zu tun, ist die Verwendung von JSON.stringfy und anschließend von JSON.parse.

In Ihrem Fall sollte dies funktionieren:

this.backupData = JSON.parse(JSON.stringify(genericItems));
jmuhire
quelle
8

Versuchen Sie den folgenden Code:

this.cloneArray= [...this.OriginalArray]
Gaurav Panwar
quelle
3

Die folgende Zeile in Ihrem Code erstellt ein neues Array, kopiert alle Objektreferenzen genericItemsin dieses neue Array und weist es zu backupData:

this.backupData = this.genericItems.slice();

Während backupDataund genericItemssind verschiedene Arrays, enthalten sie die gleichen exakten Objektreferenzen.

Sie könnten eine Bibliothek mitbringen, um tiefes Kopieren für Sie durchzuführen (wie @LatinWarrior erwähnt).

Aber wenn Itemes nicht zu komplex ist, können Sie vielleicht eine cloneMethode hinzufügen , um das Objekt selbst tief zu klonen:

class Item {
  somePrimitiveType: string;
  someRefType: any = { someProperty: 0 };

  clone(): Item {
    let clone = new Item();

    // Assignment will copy primitive types

    clone.somePrimitiveType = this.somePrimitiveType;

    // Explicitly deep copy the reference types

    clone.someRefType = {
      someProperty: this.someRefType.someProperty
    };

    return clone;
  }
}

Rufen Sie dann clone()jeden Artikel an:

this.backupData = this.genericItems.map(item => item.clone());
Frank Modica
quelle
3

Ich habe das gleiche Problem mit primeNg DataTable. Nachdem ich es versucht und geweint habe, habe ich das Problem mithilfe dieses Codes behoben.

private deepArrayCopy(arr: SelectItem[]): SelectItem[] {
    const result: SelectItem[] = [];
    if (!arr) {
      return result;
    }
    const arrayLength = arr.length;
    for (let i = 0; i <= arrayLength; i++) {
      const item = arr[i];
      if (item) {
        result.push({ label: item.label, value: item.value });
      }
    }
    return result;
  }

Zum Initialisieren des Sicherungswerts

backupData = this.deepArrayCopy(genericItems);

Zum Zurücksetzen von Änderungen

genericItems = this.deepArrayCopy(backupData);

Das Wundermittel besteht darin, Elemente mithilfe {}des Konstruktors neu zu erstellen, anstatt ihn aufzurufen. Ich habe versucht, new SelectItem(item.label, item.value)was nicht funktioniert.

mehdinf
quelle
2

Sie können die Kartenfunktion verwenden

 toArray= fromArray.map(x => x);
Amirouche Zeggagh
quelle
1

Versuche dies:

[https://lodash.com/docs/4.17.4#clone][1]

var objects = [{ 'a': 1 }, { 'b': 2 }];

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true
Lateinischer Krieger
quelle
1

Es sieht so aus, als hätten Sie einen Fehler gemacht, wo Sie die Kopie eines Arrays erstellen. Schauen Sie sich meine Erklärung unten und eine geringfügige Änderung des Codes an, die Ihnen dabei helfen soll, die Daten auf den vorherigen Status zurückzusetzen.

In Ihrem Beispiel kann ich Folgendes sehen:

  • Sie fordern generische Artikel an
  • Nachdem Sie die Daten erhalten haben, setzen Sie die Ergebnisse auf this.genericItems
  • direkt danach setzen Sie die backupData als Ergebnis

Habe ich Recht, wenn ich denke, dass Sie nicht möchten, dass der dritte Punkt in dieser Reihenfolge passiert?

Wäre das besser:

  • Sie machen die Datenanforderung
  • Erstellen Sie eine Sicherungskopie dessen, was in this.genericItems aktuell ist
  • Setzen Sie dann genericItems als Ergebnis Ihrer Anfrage

Versuche dies:

getGenericItems(selected: Item) {
  this.itemService.getGenericItems(selected).subscribe(
    result => {
       // make a backup before you change the genericItems
       this.backupData = this.genericItems.slice();

       // now update genericItems with the results from your request
       this.genericItems = result;
    });
  }
Molik Miah
quelle
1

Der folgende Code kann Ihnen beim Kopieren der Objekte der ersten Ebene helfen

let original = [{ a: 1 }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))

Für den folgenden Fall bleiben die Werte also intakt

copy[0].a = 23
console.log(original[0].a) //logs 1 -- value didn't change voila :)

Schlägt für diesen Fall fehl

let original = [{ a: {b:2} }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
copy[0].a.b = 23;
console.log(original[0].a) //logs 23 -- lost the original one :(

Abschließender Rat:

Ich würde sagen, entscheiden Sie sich für die lodash- cloneDeepAPI, mit der Sie die Objekte in Objekten kopieren können, die vollständig von den ursprünglichen Objekten abweichen . Dies kann als separates Modul installiert werden.

Siehe Dokumentation: https://github.com/lodash/lodash

Einzelpaket : https://www.npmjs.com/package/lodash.clonedeep

Nirus
quelle
1

Der einfachste Weg, ein Array zu klonen, ist

backUpData = genericItems.concat();

Dadurch wird ein neuer Speicher für die Array-Indizes erstellt

VarunJi
quelle
Dies schafft keinen neuen Speicher für backUpData. backUpDatahält noch Referenz von genericItems.
Suyash Gupta
1

Wenn Ihre Elemente im Array nicht primitiv sind, können Sie dazu den Spread-Operator verwenden.

this.plansCopy = this.plans.map(obj => ({...obj}));

Vollständige Antwort: https://stackoverflow.com/a/47776875/5775048

Gatsby
quelle
0

Sieht so aus, als ob Sie eine tiefe Kopie des Objekts wünschen . Warum nicht verwenden Object.assign()? Keine Bibliotheken benötigt, und es ist ein Einzeiler :)

getGenericItems(selected: Item) {
    this.itemService.getGenericItems(selected).subscribe(
        result => {
            this.genericItems = result;
            this.backupDate = Object.assign({}, result); 
            //this.backupdate WILL NOT share the same memory locations as this.genericItems
            //modifying this.genericItems WILL NOT modify this.backupdate
        });
}

Weitere Informationen finden Sie unterObject.assign() : https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

CosyAzure
quelle
Dies wird das Gleiche tun wie slice(). Es wird ein neues Array erstellt, aber die Objektreferenzen werden aus dem alten Array kopiert.
Frank Modica
Auch ich denke es sollte sein Object.assign([], result). Ansonsten denke ich, dass Sie das lengthEigentum verlieren werden (und vielleicht einige andere Dinge).
Frank Modica
-1

Versuche dies

const returnedTarget = Object.assign(target, source);

und übergeben Sie ein leeres Array an das Ziel

Für den Fall, dass komplexe Objekte auf diese Weise für mich funktionieren

$.extend(true, [], originalArray) im Falle eines Arrays

$.extend(true, {}, originalObject) im Falle eines Objekts

Samir Ghoneim
quelle