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