我有一个超类,它是许多子类(Customer, Product, ProductCategory…)的父类(Entity)。
我想在Typescript中动态克隆一个包含不同子对象的对象。
例如:拥有不同产品的客户拥有一个ProductCategory
var cust:Customer = new Customer ();
cust.name = "someName";
cust.products.push(new Product(someId1));
cust.products.push(new Product(someId2));
为了克隆对象的整个树,我在实体中创建了一个函数
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被转译为javascript时,将引发以下错误:错误TS2351:不能对缺少调用或构造签名的表达式使用'new'。
虽然脚本工作,但我想摆脱转译错误
自从TypeScript 3.7发布以来,现在支持递归类型别名,它允许我们定义一个类型安全的deepCopy()函数:
// DeepCopy type can be easily extended by other types,
// like Set & Map if the implementation supports them.
type DeepCopy<T> =
T extends undefined | null | boolean | string | number ? T :
T extends Function | Set<any> | Map<any, any> ? unknown :
T extends ReadonlyArray<infer U> ? Array<DeepCopy<U>> :
{ [K in keyof T]: DeepCopy<T[K]> };
function deepCopy<T>(obj: T): DeepCopy<T> {
// implementation doesn't matter, just use the simplest
return JSON.parse(JSON.stringify(obj));
}
interface User {
name: string,
achievements: readonly string[],
extras?: {
city: string;
}
}
type UncopiableUser = User & {
delete: () => void
};
declare const user: User;
const userCopy: User = deepCopy(user); // no errors
declare const uncopiableUser: UncopiableUser;
const uncopiableUserCopy: UncopiableUser = deepCopy(uncopiableUser); // compile time error
操场上
解决具体问题
你可以使用类型断言来告诉编译器你知道的更好:
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;
}
克隆
截至2022年,有一项提案允许structuredClone深度复制许多类型。
const copy = structuredClone(value)
对于你可以在什么事情上使用它有一些限制。
请记住,有时候编写自己的映射比完全动态的映射更好。然而,你可以使用一些“克隆”技巧来获得不同的效果。
我将使用以下代码的所有后续示例:
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'));
选择1:分散
属性:是的
方法:没有
深度复制:否
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
选项2:Object.assign
属性:是的
方法:没有
深度复制:否
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
选项3:Object.create
属性:继承
方法:继承
深度复制:浅继承(深度更改同时影响原始和克隆)
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
选项4:深度复制功能
属性:是的
方法:没有
深度复制:是的
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
我自己也遇到过这个问题,最后写了一个小库clone -ts,它提供了一个抽象类,它向任何扩展它的类添加了一个克隆方法。抽象类借用了芬顿接受的答案中描述的深度复制函数,只是替换了Copy = {};使用copy = object. create(originalObj)来保留原始对象的类。下面是一个使用该类的示例。
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
或者你可以直接用克隆。克隆助手方法:
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
下面是deepCopy在TypeScript中的实现(代码中不包含任何内容):
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
}