381 Stimmen

Typescript - Objekt klonen

Ich habe eine Superklasse, die das Elternelement (Entity) für viele Unterklassen (Customer, Product, ProductCategory...) ist

Ich möchte in Typescript dynamisch ein Objekt klonen, das verschiedene Teilobjekte enthält.

Zum Beispiel: ein Customer, der verschiedene Product enthält, die eine ProductCategory haben

var cust:Customer = new Customer();

cust.name = "someName";
cust.products.push(new Product(someId1));
cust.products.push(new Product(someId2));

Um den gesamten Objektbaum zu klonen, habe ich eine Funktion in Entity erstellt

public clone():any {
    var cloneObj = new this.constructor();
    for (var attribut in this) {
        if(typeof this[attribut] === "object"){
           cloneObj[attribut] = this.clone();
        } else {
           cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

Der new Fehler tritt auf, wenn er zu Javascript transpiliert wird: Fehler TS2351: Kann 'new' nicht mit einem Ausdruck verwenden, dessen Typ über eine Aufruf- oder Konstruktions-Signatur verfügt.

Obwohl das Skript funktioniert, möchte ich den transpilierten Fehler loswerden

497voto

Fenton Punkte 221749

Das spezifische Problem lösen

Sie können eine Typassertion verwenden, um dem Compiler zu sagen, dass Sie es besser wissen:

public clone(): any {
    var cloneObj = new (this.constructor() as any);
    for (var attribut in this) {
        if (typeof this[attribut] === "object") {
            cloneObj[attribut] = this[attribut].clone();
        } else {
            cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

Klonen

Ab 2022 gibt es einen Vorschlag, structuredClone zu verwenden, um viele Typen tief zu kopieren.

const copy = structuredClone(value)

Es gibt einige Einschränkungen, auf welche Art von Objekten dies angewendet werden kann.

Beachten Sie, dass es manchmal besser ist, Ihre eigene Zuordnung zu schreiben - eher als vollständig dynamisch zu sein. Es gibt jedoch einige "Klon"-Tricks, die Sie verwenden können, um verschiedene Effekte zu erzielen.

Ich werde den folgenden Code für alle nachfolgenden Beispiele verwenden:

class Example {
  constructor(public type: string) {

  }
}

class Customer {
  constructor(public name: string, public example: Example) {

  }

  greet() {
    return 'Hallo ' + this.name;
  }
}

var customer = new Customer('David', new Example('DavidType'));

Option 1: Spread

Eigenschaften: Ja
Methoden: Nein
Tiefenkopie: Nein

var clone = { ...customer };

alert(clone.name + ' ' + clone.example.type); // David DavidType
//alert(clone.greet()); // Nicht OK

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David SteveType

Option 2: Object.assign

Eigenschaften: Ja
Methoden: Nein
Tiefenkopie: Nein

var clone = Object.assign({}, customer);

alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // Nicht OK, obwohl der Compiler es nicht bemerken wird

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David SteveType

Option 3: Object.create

Eigenschaften: Vererbt
Methoden: Vererbt
Tiefenkopie: Flach vererbt (tiefe Änderungen betreffen sowohl das Original als auch den Klon)

var clone = Object.create(customer);

alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // OK

customer.name = 'Misha';
customer.example = new Example("MishaType");

// Der Klon sieht Änderungen am Original
alert(clone.name + ' ' + clone.example.type); // Misha MishaType

clone.name = 'Steve';
clone.example.type = 'SteveType';

// Das Original sieht Änderungen am Klon
alert(customer.name + ' ' + customer.example.type); // Misha SteveType

Option 4: Deep Copy Function

Eigenschaften: Ja
Methoden: Nein
Tiefenkopie: Ja

function deepCopy(obj) {
    var copy;

    // Behandle die 3 einfachen Typen, sowie null oder undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Behandle Datum
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Behandle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = deepCopy(obj[i]);
        }
        return copy;
    }

    // Behandle Objekt
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]);
        }
        return copy;
    }

    throw new Error("Kann das Objekt nicht kopieren! Der Typ wird nicht unterstützt.");
}

var clone = deepCopy(customer) as Customer;

alert(clone.name + ' ' + clone.example.type); // David DavidType
// alert(clone.greet()); // Nicht OK - nicht wirklich ein Kunde

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David DavidType

291voto

Maciej Sikora Punkte 17478
  1. Verwenden Sie den Spread Operator ...

     const obj1 = { param: "value" };
     const obj2 = { ...obj1 };

Der Spread Operator nimmt alle Felder von obj1 und verteilt sie über obj2. Als Ergebnis erhalten Sie ein neues Objekt mit einem neuen Verweis und den gleichen Feldern wie im Original.

Denken Sie daran, dass es sich um eine flache Kopie handelt, das bedeutet, dass wenn ein Objekt verschachtelt ist, dann werden seine verschachtelten zusammengesetzten Parameter im neuen Objekt mit dem gleichen Verweis existieren.

  1. Object.assign()

     const obj1={ param: "value" };
     const obj2:any = Object.assign({}, obj1);

Object.assign erstellt eine echte Kopie, aber nur eigene Eigenschaften, daher werden Eigenschaften im Prototyp nicht im kopierten Objekt existieren. Es handelt sich ebenfalls um eine flache Kopie.


  1. Object.create()

     const obj1={ param: "value" };
     const obj2:any = Object.create(obj1);

Object.create klont nicht wirklich, es erstellt ein Objekt aus dem Prototyp. Verwenden Sie es also, wenn das Objekt primäre Typ-Eigenschaften klonen soll, da die Zuweisung von primären Typ-Eigenschaften nicht per Verweis erfolgt.

Vorteile von Object.create sind, dass alle Funktionen, die im Prototyp deklariert sind, in unserem neu erstellten Objekt verfügbar sein werden.


Ein paar Dinge über flache Kopien

Flache Kopien fügen alle Felder des alten Objekts in das neue Objekt ein, aber das bedeutet auch, dass wenn das originale Objekt zusammengesetzte Typ-Felder (Objekte, Arrays usw.) hat, dann werden diese Felder im neuen Objekt mit den gleichen Verweisen eingefügt. Eine Mutation eines solchen Feldes im ursprünglichen Objekt wird im neuen Objekt widergespiegelt.

Es mag wie eine Fallstrick aussehen, aber tatsächlich ist die Situation, in der das gesamte komplexe Objekt kopiert werden muss, selten. Eine flache Kopie wird den Großteil des Speichers wiederverwenden, was bedeutet, dass sie im Vergleich zu einer tiefen Kopie sehr kostengünstig ist.


Tiefe Kopie

Der Spread Operator kann nützlich für tiefe Kopien sein.

const obj1 = { param: "value", complex: { name: "John"}}
const obj2 = { ...obj1, complex: {...obj1.complex}};

Der obige Code erstellte eine tiefe Kopie von obj1. Das zusammengesetzte Feld "complex" wurde auch in obj2 kopiert. Eine Mutation des Feldes "complex" wird die Kopie nicht widerspiegeln.

93voto

Lars Punkte 1835

Versuchen Sie dies:

let copy = (JSON.parse(JSON.stringify(objectToCopy)));

Es ist eine gute Lösung, solange Sie sehr große Objekte verwenden oder Ihr Objekt nicht serialisierbare Eigenschaften hat.

Um die Typsicherheit zu erhalten, könnten Sie eine Kopierfunktion in der Klasse verwenden, von der Sie Kopien erstellen möchten:

getCopy(): YourClassName{
    return (JSON.parse(JSON.stringify(this)));
}

oder auf statische Weise:

static createCopy(objectToCopy: YourClassName): YourClassName{
    return (JSON.parse(JSON.stringify(objectToCopy)));
}

62voto

Luca C. Punkte 9744

TypeScript/JavaScript hat seinen eigenen Operator für das Flachkopieren:

let shallowClone = { ...original };

20voto

Polv Punkte 1606

Für einen serialisierbaren Deep Clone, mit Typeninformation ist,

export function clone(a: T): T {
  return JSON.parse(JSON.stringify(a));
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X