javascript - Djupkloning i TypeScript (bevara typer)


Jag behöver göra en djup kloning av ett objekt i TypeScript. Detta borde inte vara ett problem eftersom bibliotek som Lodash tillhandahåller lämpliga funktioner för det. Dock verkar dessa kassera typinformationen.

> var a = new SomeClass();
> a instanceof SomeClass;
< true
> var b = _.cloneDeep(a);
> b instanceof SomeClass;
< false

Finns det ett sätt att klona objekt i TypeScript samtidigt som denna typinformation bevaras?

Svar #1

Typescript släpper inte typinformationen här. I DefinitelyTyped lodash.d.ts-filen kan du se att cloneDeep definieras som

cloneDeep<T>(
    val: T,
    customizer?: (value: any) => any,
    thisArg?: any
) : T

Om vi ignorerar de argument vi inte bryr oss om, tar det en T som indata, och ger ut en T som utdata. Så Typescript förlorar ingen typinformation; det betraktar utdata från cloneDeep som samma typ som indatan.

Du borde kunna verifiera detta via din redigerare: förutsatt att du har någon redigerare som låter dig inspektera typen av variabler eller autokompletterar metoder (vilket jag starkt skulle rekommendera, om du inte har det).

Varför fungerar då inte typeof som du förväntar dig? Det beror på att Typescript-typinformationen inte förs vidare till runtime. instanceof är en inbyggd JS-operator, vars beteende Typescript inte ändrar, vilket du kan se genom att köra detta kodexempel:

"use strict";
class A {}

let a = new A();
let b = _.cloneDeep(a);

if (b instanceof A) {
  alert("b is an instance of A");
} else {
  alert("b is not an instance of A");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>

Anledningen till att b instanceof A är falskt är att instanceof kontrollerar mot konstruktörer: x instanceof A returnerar sant om funktionen A är en konstruktör någonstans i xs prototypkedja (se MDN-dokumentationen om instanceof). Lodash använder dock inte konstruktörer när det klonar objekt. Det kan det inte. (Hur skulle det veta vilka argument som ska skickas?) Det skapar ett vanligt JS-objekt som har alla metoder från det klonade objektet, men återskapar inte dess prototypkedja.

Lodashs clone (och de flesta av Lodashs metoder, egentligen) används bäst när man hanterar råa JS-objekt. Om du använder det tillsammans med konstruktörer och instanceof-kontroller blir saker lite otydliga.

En lösning här är att undvika instanceof-kontroller och göra något som liknar duck typing; kontrollera inte att objektets konstruktor är en viss funktion, utan kontrollera att objektet har de egenskaper som du förväntar dig att det ska ha.

En annan lösning är, som föreslaget i kommentarerna, att implementera en kloningsmetod på din klass själv, vilket inte skulle använda lodash.

class A() {
    clone() {
        var cloned = new A(); //pass appropriate constructor args
        //make other necessary changes to make the state match
        return cloned;
    }
}

Svar #2

Du kan använda Lodash#cloneDeep-verktyget. Exempel på användning:

import * as _ from "lodash";

...

{
    this.cloned = _.cloneDeep(data);
}

Relaterade artiklar