wake-up-neo.com

typoskript - Klonierungsobjekt

Ich habe eine übergeordnete Klasse (Entity) für viele Unterklassen (Customer, Product, ProductCategory...).

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

Im Beispiel: eine Customer, die verschiedene Product hat, die eine ProductCategory hat

var cust:Customer  = new Customer ();

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

Um den gesamten Baum eines Objekts 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;
}

Die new gibt den folgenden Fehler aus, wenn sie in Javascript übersetzt wird: error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.

Obwohl das Skript funktioniert, möchte ichden transpilierten Fehlerentfernen.

96
David Laberge

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 (<any>this.constructor());
    for (var attribut in this) {
        if (typeof this[attribut] === "object") {
            cloneObj[attribut] = this.clone();
        } else {
            cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

Klonen

Beachten Sie, dass es manchmal besser ist, ein eigenes Mapping zu schreiben, als völlig dynamisch zu sein. Es gibt jedoch ein paar "Klonen" -Tricks, die Sie verwenden können, um unterschiedliche 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 'Hello ' + this.name;
  }
}

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

Option 1: Spread

Eigenschaften: Ja
Methoden: Nein
Deep Copy: Nein 

var clone = { ...customer };

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

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

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

Option 2: Object.assign

Eigenschaften: Ja
Methoden: Nein
Deep Copy: Nein 

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

alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // Not OK, although compiler won't spot it

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

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

Option 3: Object.create

Eigenschaften: Ja
Methoden: Ja
Deep Copy: Nein 

var clone = Object.create(customer);

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

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

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

Option 4: Deep Copy-Funktion

Eigenschaften: Ja
Methoden: Nein
Deep Copy: Ja  

function deepCopy(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

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

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

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

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

var clone = <Customer>deepCopy(customer);

alert(clone.name + ' ' + clone.example.type); // David DavidType
// alert(clone.greet()); // Not OK - not really a customer

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

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

1. Spread-Operator verwenden

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

Der Spread-Operator nimmt alle Felder aus obj1 und verteilt sie über obj2. Im Ergebnis erhalten Sie ein neues Objekt mit neuer Referenz und denselben Feldern wie das ursprüngliche. 

Denken Sie daran, dass es sich um eine flache Kopie handelt. Wenn das Objekt verschachtelt ist, werden die verschachtelten zusammengesetzten Parameter im neuen Objekt mit demselben Verweis vorhanden sein.

2.Object.assign ()

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

Object.assign Erstellen Sie eine echte Kopie, aber nur eigene Eigenschaften, sodass die Eigenschaften im Prototyp nicht in dem kopierten Objekt vorhanden sind. Es ist auch eine flache Kopie.


3.Object.create ()

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

Object.create macht kein echtes Klonen , er erstellt ein Objekt aus einem Prototyp. Verwenden Sie es also, wenn das Objekt primäre Eigenschaften klonen soll, da die Zuordnung der primären Eigenschaften nicht per Referenz erfolgt.

Pluspunkte von Object.create sind, dass alle in prototype deklarierten Funktionen in unserem neu erstellten Objekt verfügbar sind.


Wenige Dinge über flaches Exemplar

Durch die flache Kopie werden alle Felder des alten in ein neues Objekt eingefügt. Wenn das ursprüngliche Objekt jedoch zusammengesetzte Felder (Objekt, Arrays usw.) enthält, werden diese Felder in ein neues Objekt mit den gleichen Referenzen eingefügt. Die Mutation dieses Feldes im ursprünglichen Objekt wird im neuen Objekt wiedergegeben. 

Es sieht vielleicht wie eine Falle aus, aber tatsächlich ist es selten, wenn das gesamte komplexe Objekt kopiert werden muss. Bei flacher Kopie wird der größte Teil des Speichers wiederverwendet, was im Vergleich zu Deep Copy sehr günstig ist.


Tiefe Kopie

Der Spread-Operator kann für tiefes Kopieren nützlich sein.

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

Über dem Code wurde eine tiefe Kopie von obj1 erstellt. Das zusammengesetzte Feld "complex" wurde ebenfalls in obj2 kopiert. Das Mutationsfeld "complex" gibt die Kopie nicht wieder.

127
Maciej Sikora

Versuche dies:

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

Dies ist eine gute Lösung, bis Sie sehr große Objekte verwenden oder Ihr Objekt über nichtialisierbare Eigenschaften verfügt.

Um die Textsicherheit zu erhalten, können Sie eine Kopierfunktion in der Klasse verwenden, aus 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)));
}
27
Lars

TypeScript/Javascript hat einen eigenen Operator für das flache Klonen:

let shallowClone = { ...original };
20
Luca C.

Mit "Object Spread", das in TypeScript 2.1 eingeführt wurde, ist es leicht, eine flache Kopie zu erhalten

dieses TypeScript: let copy = { ...original };

erzeugt dieses JavaScript:

var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
var copy = __assign({}, original);

https://www.typescriptlang.org/docs/handbook/release-notes/TypeScript-2-1.html

14
Homer

Sie können auch so etwas haben:

class Entity {
    id: number;

    constructor(id: number) {
        this.id = id;
    }

    clone(): this {
        return new (this.constructor as typeof Entity)(this.id) as this;
    }
}

class Customer extends Entity {
    name: string;

    constructor(id: number, name: string) {
        super(id);
        this.name = name;
    }

    clone(): this {
        return new (this.constructor as typeof Customer)(this.id, this.name) as this;
    }
}

Stellen Sie einfach sicher, dass Sie die clone-Methode in allen Entity-Unterklassen überschreiben. Andernfalls erhalten Sie teilweise Klone.

Der Rückgabetyp von this stimmt immer mit dem Typ der Instanz überein.

4
Decade Moon

Für serialisierbare tiefe Klone mit Typinformationen lautet:

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

Kam über dieses Problem hinweg und schrieb am Ende eine kleine Bibliothek cloneable-ts , die eine abstrakte Klasse bereitstellt, die jeder Klasse, die sie erweitert, eine Klonmethode hinzufügt. Die abstrakte Klasse entlehnt die in der akzeptierten Antwort von Fenton beschriebene Deep Copy-Funktion und ersetzt nur copy = {}; durch copy = Object.create(originalObj), um die Klasse des ursprünglichen Objekts zu erhalten. Hier ist ein Beispiel für die Verwendung der Klasse.

import {Cloneable, CloneableArgs} from 'cloneable-ts';

// Interface that will be used as named arguments to initialize and clone an object
interface PersonArgs {
    readonly name: string;
    readonly age: number;
}

// Cloneable abstract class initializes the object with super method and adds the clone method
// CloneableArgs interface ensures that all properties defined in the argument interface are defined in class
class Person extends Cloneable<TestArgs>  implements CloneableArgs<PersonArgs> {
    readonly name: string;
    readonly age: number;

    constructor(args: TestArgs) {
        super(args);
    }
}

const a = new Person({name: 'Alice', age: 28});
const b = a.clone({name: 'Bob'})
a.name // Alice
b.name // Bob
b.age // 28

Oder Sie können einfach die Cloneable.clone-Hilfsmethode verwenden:

import {Cloneable} from 'cloneable-ts';

interface Person {
    readonly name: string;
    readonly age: number;
}

const a: Person = {name: 'Alice', age: 28};
const b = Cloneable.clone(a, {name: 'Bob'})
a.name // Alice
b.name // Bob
b.age // 28    
2
Tim Osadchiy

Meine Meinung dazu:

Object.assign(...) kopiert nur Eigenschaften und wir verlieren den Prototyp und die Methoden.

Object.create(...) kopiert keine Eigenschaften für mich und erstellt nur einen Prototyp.

Was für mich funktionierte, war das Erstellen eines Prototyps mit Object.create(...) und das Kopieren von Eigenschaften mit Object.assign(...) dorthin:

Machen Sie für ein Objekt foo einen Klon wie folgt:

Object.assign(Object.create(foo), foo)
2
Muhammad Ali

Für einen einfachen Klon des Inhalts des Bohrungsobjekts wird die Instanz einfach stringifiziert und analysiert:

let cloneObject = JSON.parse(JSON.stringify(objectToClone))

Während ich Daten im objectToClone-Baum ändere, ändert sich das cloneObject nicht. Das war meine Forderung.

Ich hoffe es hilft

1
Ferhatos

Wenn Sie diesen Fehler erhalten:

TypeError: this.constructor(...) is not a function

Dies ist das richtige Skript:

public clone(): any {
    var cloneObj = new (<any>this.constructor)(); // line fixed
    for (var attribut in this) {
        if (typeof this[attribut] === "object") {
            cloneObj[attribut] = this.clone();
        } else {
            cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}
1
pablorsk

Ich habe versucht, einen allgemeinen Kopier-/Klondienst zu erstellen, der Typen für verschachtelte Objekte beibehält. Würde mich über Feedback freuen, wenn ich etwas falsch mache, aber es scheint soweit zu funktionieren ...

import { Injectable } from '@angular/core';

@Injectable()
export class CopyService {

  public deepCopy<T>(objectToClone: T): T {
    // If it's a simple type or null, just return it.
    if (typeof objectToClone === 'string' ||
      typeof objectToClone === 'number' ||
      typeof objectToClone === 'undefined' ||
      typeof objectToClone === 'symbol' ||
      typeof objectToClone === 'function' ||
      typeof objectToClone === 'boolean' ||
      objectToClone === null
    ) {
      return objectToClone;
    }

    // Otherwise, check if it has a constructor we can use to properly instantiate it...
    let ctor = Object.getPrototypeOf(objectToClone).constructor;
    if (ctor) {
      let clone = new ctor();

      // Once we've instantiated the correct type, assign the child properties with deep copies of the values
      Object.keys(objectToClone).forEach(key => {
        if (Array.isArray(objectToClone[key]))
          clone[key] = objectToClone[key].map(item => this.deepCopy(item));
        else
          clone[key] = this.deepCopy(objectToClone[key]);
      });

      if (JSON.stringify(objectToClone) !== JSON.stringify(clone))
        console.warn('object cloned, but doesnt match exactly...\nobject: ' + JSON.stringify(objectToClone) + "\nclone: " + JSON.stringify(clone))

      // return our cloned object...
      return clone;
    }
    else {
      //not sure this will ever get hit, but figured I'd have a catch call.
      console.log('deep copy found something it didnt know: ' + JSON.stringify(objectToClone));
      return objectToClone;
    }
  }
}
0
patrickbadley

Wie wäre es mit guten alten jQuery ?! Hier ist ein tiefer Klon:

var clone = $.extend(true, {}, sourceObject);
0
alehro

Hier ist mein Mash-Up! Und hier ist ein StackBlitz-Link dazu. Derzeit beschränkt es sich darauf, nur einfache Typen und Objekttypen zu kopieren, könnte aber leicht geändert werden, denke ich.

   let deepClone = <T>(source: T): { [k: string]: any } => {
      let results: { [k: string]: any } = {};
      for (let P in source) {
        if (typeof source[P] === 'object') {
          results[P] = deepClone(source[P]);
        } else {
          results[P] = source[P];
        }
      }
      return results;
    };
0
marckassay

Ich endete mit:

public clone(): any {
  const result = new (<any>this.constructor);

  // some deserialization code I hade in place already...
  // which deep copies all serialized properties of the
  // object graph
  // result.deserialize(this)

  // you could use any of the usggestions in the other answers to
  // copy over all the desired fields / properties

  return result;
}

Weil:

var cloneObj = new (<any>this.constructor());

von @Fenton gab Laufzeitfehler.

TypeScript-Version: 2.4.2

0
Bernoulli IT

Fügen Sie "lodash.clonedeep": "^4.5.0" zu Ihrem package.json hinzu. Dann benutze wie folgt:

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)
0
user2878850