javascript - typescript - kloning av objekt


Jeg har en superklasse som er forelder (Entity) for mange underklasser (Customer, Product, ProductCategory...)

Jeg ser etter å klone dynamisk et objekt som inneholder forskjellige underobjekter i Typescript.

I eksemplet: en Customer som har forskjellige Product som har en ProductCategory

var cust:Customer  = new Customer ();

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

For å klone hele objekt-treet laget jeg en funksjon i Entity

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

new gir følgende feil når det transpileres til JavaScript: error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.

Selv om skriptet fungerer, vil jeg gjerne bli kvitt den transpilerte feilen

Svar #1

Løse det spesifikke problemet

Du kan bruke en typepåstand for å fortelle kompilatoren at du vet bedre:

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

Kloning

Fra og med 2022 er det et forslag om å tillate structuredClone å foreta dypkopiering av mange typer.

const copy = structuredClone(value)

Det er noen begrensninger på hva slags ting du kan bruke dette på.

Husk at det noen ganger er bedre å skrive din egen kartlegging - i stedet for å være helt dynamisk. Det finnes imidlertid noen få "kloning"-triks du kan bruke som gir deg forskjellige effekter.

Jeg vil bruke følgende kode for alle påfølgende eksempler:

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

Alternativ 1: Spre

Egenskaper: Ja
Metoder: Nei
Dyp kopi: Nei

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

Alternativ 2: Object.assign

Egenskaper: Ja
Metoder: Nei
Dyp kopi: Nei

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

Alternativ 3: Object.create

Egenskaper: Arvet
Metoder: Arvet
Dyp kopi: Grunnleggende arvet (dype endringer påvirker både original og klone)

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

// clone sees changes to original 
alert(clone.name + ' ' + clone.example.type); // Misha MishaType

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

// original sees changes to clone
alert(customer.name + ' ' + customer.example.type); // Misha SteveType

Alternativ 4: Dypkopieringsfunksjon

Egenskaper: Ja
Metoder: Nei
Dyp kopi: 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 = deepCopy(customer) as 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

Svar #2

  1. Bruk spredningsoperator ...

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

Spread-operatoren tar alle feltene fra obj1 og sprer dem over obj2. I resultatet får du et nytt objekt med ny referanse og de samme feltene som det opprinnelige.

Husk at det er en grunn kopi, det betyr at hvis objektet er innebygd, vil de innebygde sammensatte parameterne eksistere i det nye objektet ved samme referanse.

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

Object.assign lager en ekte kopi, men kun egne egenskaper, så egenskaper i prototypen vil ikke eksistere i det kopierte objektet. Det er også en overfladisk kopi.


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

Object.create utfører ikke ekte kloning, det oppretter et objekt fra en prototype. Så bruk det hvis objektet skal klone primære typegenskaper, fordi tildeling av primære typegenskaper ikke gjøres ved referanse.

Fordelene med Object.create er at alle funksjoner som er deklarert i prototype vil være tilgjengelige i vårt nyopprettede objekt.


Noen ting om overfladisk kopi

En grunn kopi legger alle felt i det gamle objektet inn i et nytt objekt, men det betyr også at hvis det originale objektet har sammensatte felter (objekt, matriser osv.), så blir disse feltene lagt inn i det nye objektet med de samme referansene. Endring av et slikt felt i det originale objektet vil bli reflektert i det nye objektet.

Det kan kanskje se ut som en fallgruve, men situasjonen der hele komplekse objekter må kopieres er egentlig sjelden. En grunn kopi vil gjenbruke mesteparten av minnet, noe som betyr at det er veldig billig sammenlignet med dypt kopi.


Dyp kopi

Spread-operatoren kan være nyttig for dypkopiering.

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

Ovenstående kode opprettet en dyp kopi av obj1. Sammensatt felt "complex" ble også kopiert til obj2. Endringer i feltet "complex" vil ikke bli reflektert i kopien.

Svar #3

Prøv dette:

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

Det er en god løsning så lenge du ikke bruker veldig store objekter eller objektet ditt har ikke-serialiserbare egenskaper.

For å bevare typesikkerhet kan du bruke en kopieringsfunksjon i klassen du vil lage kopier fra:

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

eller på en statisk måte:

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

Svar #4

TypeScript/JavaScript har sin egen operator for grunt kloning:

let shallowClone = { ...original };

Svar #5

For serialiserbar dyp kloning, med typeinformasjon er,

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

Svar #6

Det er lett å få en overfladisk kopi med «Object Spread» introdusert i TypeScript 2.1

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

produserer denne JavaScripten:

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

Svar #7

Her er deepCopy-implementeringen i TypeScript (ingen any er inkludert i koden):

const deepCopy = <T, U = T extends Array<infer V> ? V : never>(source: T ): T => {
  if (Array.isArray(source)) {
    return source.map(item => (deepCopy(item))) as T & U[]
  }
  if (source instanceof Date) {
    return new Date(source.getTime()) as T & Date
  }
  if (source && typeof source === 'object') {
    return (Object.getOwnPropertyNames(source) as (keyof T)[]).reduce<T>((o, prop) => {
      Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(source, prop)!)
      o[prop] = deepCopy(source[prop])
      return o
    }, Object.create(Object.getPrototypeOf(source)))
  }
  return source
}

Svar #8

Legg til "lodash.clonedeep": "^4.5.0" i din package.json. Bruk deretter slik:

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)

Relaterte artikler