我有一个超类,它是许多子类(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'。
虽然脚本工作,但我想摆脱转译错误
试试这个:
let copy = (JSON.parse(JSON.stringify(objectToCopy)));
这是一个很好的解决方案,直到您使用非常大的对象或对象具有不可序列化的属性。
为了保持类型安全,你可以在你想复制的类中使用一个copy函数:
getCopy(): YourClassName{
return (JSON.parse(JSON.stringify(this)));
}
或以静态的方式:
static createCopy(objectToCopy: YourClassName): YourClassName{
return (JSON.parse(JSON.stringify(objectToCopy)));
}
对于深度克隆对象,可以包含另一个对象,数组等,我使用:
const clone = <T>(source: T): T => {
if (source === null) return source
if (source instanceof Date) return new Date(source.getTime()) as any
if (source instanceof Array) return source.map((item: any) => clone<any>(item)) as any
if (typeof source === 'object' && source !== {}) {
const clonnedObj = { ...(source as { [key: string]: any }) } as { [key: string]: any }
Object.keys(clonnedObj).forEach(prop => {
clonnedObj[prop] = clone<any>(clonnedObj[prop])
})
return clonnedObj as T
}
return source
}
Use:
const obj = {a: [1,2], b: 's', c: () => { return 'h'; }, d: null, e: {a:['x'] }}
const objClone = clone(obj)
自从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
操场上
下面是一个现代的实现,它也解释了Set和Map:
export function deepClone<T extends object>(value: T): T {
if (typeof value !== 'object' || value === null) {
return value;
}
if (value instanceof Set) {
return new Set(Array.from(value, deepClone)) as T;
}
if (value instanceof Map) {
return new Map(Array.from(value, ([k, v]) => [k, deepClone(v)])) as T;
}
if (value instanceof Date) {
return new Date(value) as T;
}
if (value instanceof RegExp) {
return new RegExp(value.source, value.flags) as T;
}
return Object.keys(value).reduce((acc, key) => {
return Object.assign(acc, { [key]: deepClone(value[key]) });
}, (Array.isArray(value) ? [] : {}) as T);
}
尝试一下:
deepClone({
test1: { '1': 1, '2': {}, '3': [1, 2, 3] },
test2: [1, 2, 3],
test3: new Set([1, 2, [1, 2, 3]]),
test4: new Map([['1', 1], ['2', 2], ['3', 3]])
});
test1:
1: 1
2: {}
3: [1, 2, 3]
test2: Array(3)
0: 1
1: 2
2: 3
test3: Set(3)
0: 1
1: 2
2: [1, 2, 3]
test4: Map(3)
0: {"1" => 1}
1: {"2" => 2}
2: {"3" => 3}