Typescript克隆对象

我有一个超类,它是许多子类(CustomerProductProductCategory…)的父类(Entity)。

我想在Typescript中动态克隆一个包含不同子对象的对象。

例如:具有不同ProductCustomer具有ProductCategory

var cust:Customer  = new Customer ();


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

为了克隆对象的整个树,我在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在被转译为javascript时将引发以下错误

虽然脚本工作,我想摆脱转译错误 . exe

545969 次浏览

解决具体问题

你可以使用类型断言来告诉编译器你知道的更好:

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:分散

属性:是的
方法:No
深拷贝:No

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

属性:是的
方法:No
深拷贝:No

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:深度复制功能

属性:是的
方法:No
深层复制:是的

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

如果你得到这个错误:

TypeError: this.constructor(...) is not a function

这是正确的脚本:

public clone(): any {
var cloneObj = new (<any>this.constructor)(); // line fixed
for (var attribut in this) {
if (typeof this[attribut] === "object") {
cloneObj[attribut] = this[attribut].clone();
} else {
cloneObj[attribut] = this[attribut];
}
}
return cloneObj;
}

你也可以有这样的东西:

class Entity {
id: number;


constructor(id: number) {
this.id = id;
}


clone(): this {
return new (this.constructor as typeof Entity)(this.id) as this;
}
}


class Customer extends Entity {
name: string;


constructor(id: number, name: string) {
super(id);
this.name = name;
}


clone(): this {
return new (this.constructor as typeof Customer)(this.id, this.name) as this;
}
}

只要确保你在所有Entity子类中覆盖了clone方法,否则你最终会得到部分克隆。

this的返回类型总是与实例的类型匹配。

  1. 使用扩展运算符...

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

展开运算符将obj1中的所有字段展开到obj2上。在结果中,您将获得具有新引用的新对象和与原始对象相同的字段。

记住,这是浅复制,这意味着如果对象是嵌套的,那么它的嵌套复合参数将通过相同的引用存在于新对象中。

  1. < p > Object.assign()

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

Object.assign创建真实的副本,但只有自己的属性,因此原型中的属性将不存在于被复制的对象中。这也是浅拷贝。


  1. < p > Object.create()

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

Object.create 做的不是真正的克隆吗,它从原型创建对象。因此,如果对象应该克隆主类型属性,请使用它,因为主类型属性的赋值不是通过引用完成的。

Object.create的优点是在prototype中声明的任何函数都可以在新创建的对象中使用。


关于浅复制的一些事情

浅拷贝将旧对象的所有字段放入新对象中,但这也意味着如果原始对象具有复合类型字段(对象,数组等),那么这些字段将放入具有相同引用的新对象中。原物体中这种磁场的突变会在新物体中反映出来。

这可能看起来像一个陷阱,但真正需要复制整个复杂对象的情况是罕见的。浅拷贝将重用大部分内存,这意味着与深拷贝相比非常便宜。


深拷贝

展开运算符可以方便地进行深度复制。

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

上面的代码创建了obj1的深度拷贝。复合场“复杂”;也被复制到obj2中。突变场“复杂”;不会反映副本。

通过在TypeScript 2.1中引入的“Object Spread”,很容易获得一个浅拷贝

这个TypeScript: let copy = { ...original };

生成这个JavaScript:

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

试试这个:

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

对于一个简单的克隆的hole对象的内容,我简单地stringify和解析实例:

let cloneObject = JSON.parse(JSON.stringify(objectToClone))

当我在objectToClone树中更改数据时,在cloneObject中没有更改。这就是我的要求。

希望能有所帮助

我自己也遇到过这个问题,最后写了一个小库cloneable-ts,它提供了一个抽象类,它向任何扩展它的类添加了一个克隆方法。抽象类借用了深度复制函数(Deep Copy Function),该函数在芬顿接受的答案中描述,只是将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

或者你也可以使用Cloneable.clone helper方法:

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

TypeScript/JavaScript有自己的浅克隆操作符:

let shallowClone = { ...original };

最后我这样做了:

public clone(): any {
const result = new (<any>this.constructor);


// some deserialization code I hade in place already...
// which deep copies all serialized properties of the
// object graph
// result.deserialize(this)


// you could use any of the usggestions in the other answers to
// copy over all the desired fields / properties


return result;
}

因为:

var cloneObj = new (<any>this.constructor());

@Fenton给出了运行时错误。

Typescript版本:2.4.2

这是我的混搭!这里是它的StackBlitz链接。它目前仅限于复制简单的类型和对象类型,但我认为可以很容易地修改。

   let deepClone = <T>(source: T): { [k: string]: any } => {
let results: { [k: string]: any } = {};
for (let P in source) {
if (typeof source[P] === 'object') {
results[P] = deepClone(source[P]);
} else {
results[P] = source[P];
}
}
return results;
};

我的看法是:

Object.assign(...)只复制属性,我们丢失了原型和方法。

Object.create(...)不是为我复制属性,只是创建一个原型。

对我有用的是使用Object.create(...)创建一个原型,并使用Object.assign(...)将属性复制到它:

因此,对于foo对象,像这样进行克隆:

Object.assign(Object.create(foo), foo)

jQuery怎么样?!下面是深度克隆:

var clone = $.extend(true, {}, sourceObject);

我尝试创建一个通用的复制/克隆服务,为嵌套对象保留类型。会爱反馈,如果我做错了什么,但它似乎工作到目前为止…

import { Injectable } from '@angular/core';


@Injectable()
export class CopyService {


public deepCopy<T>(objectToClone: T): T {
// If it's a simple type or null, just return it.
if (typeof objectToClone === 'string' ||
typeof objectToClone === 'number' ||
typeof objectToClone === 'undefined' ||
typeof objectToClone === 'symbol' ||
typeof objectToClone === 'function' ||
typeof objectToClone === 'boolean' ||
objectToClone === null
) {
return objectToClone;
}


// Otherwise, check if it has a constructor we can use to properly instantiate it...
let ctor = Object.getPrototypeOf(objectToClone).constructor;
if (ctor) {
let clone = new ctor();


// Once we've instantiated the correct type, assign the child properties with deep copies of the values
Object.keys(objectToClone).forEach(key => {
if (Array.isArray(objectToClone[key]))
clone[key] = objectToClone[key].map(item => this.deepCopy(item));
else
clone[key] = this.deepCopy(objectToClone[key]);
});


if (JSON.stringify(objectToClone) !== JSON.stringify(clone))
console.warn('object cloned, but doesnt match exactly...\nobject: ' + JSON.stringify(objectToClone) + "\nclone: " + JSON.stringify(clone))


// return our cloned object...
return clone;
}
else {
//not sure this will ever get hit, but figured I'd have a catch call.
console.log('deep copy found something it didnt know: ' + JSON.stringify(objectToClone));
return objectToClone;
}
}
}

"lodash.clonedeep": "^4.5.0"添加到你的package.json。然后像这样使用:

import * as _ from 'lodash';


...


const copy = _.cloneDeep(original)

对于可序列化的深度克隆,类型信息为,

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

如果你已经有了目标对象,所以你不想重新创建它(就像更新一个数组一样)你必须复制属性 如果已经这样做:

Object.keys(source).forEach((key) => {
copy[key] = source[key]
})

表扬是应有的。(请看标题“版本2”)

在typeScript中,我用angular进行了测试,结果还不错

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] = this.deepCopy(obj[i]);
}
return copy;
}


// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = this.deepCopy(obj[attr]);
}
return copy;
}


throw new Error("Unable to copy obj! Its type isn't supported.");
}

对于深度克隆对象,可以包含另一个对象,数组等,我使用:

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
}

使用:

const obj = {a: [1,2], b: 's', c: () => { return 'h'; }, d: null, e: {a:['x'] }}
const objClone = clone(obj)

你可以使用解构的任务传播的语法:

var obj = {id = 1, name = 'product1'};
var clonedObject = {...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


<一个href = " https://www.typescriptlang.org/play/?ssl=28& ssc = 1, pln = 1,电脑= 1 #代码/ C4TwDgpgBAIhFgMIHswgDwBUB8UC8AUFMVJlBAB7AQB2AJgM5QCu9EAZgJY0R1QA % 20 ugswa2igvabgyzcigbdghibaattwdme4qftjevvad8pkac4ijmpwr0mamvybjyj2rlbazqjb0iklicallyyh40iaa0up64jqwa1jtiao5kfismnrsmuabkcntuiiaagqqq8hjc7azqakpxuowvghaikgjojbgzjadeuadaanjq3fajecdi7kqaumaw8eiogjij87gavgdcbatszq7uuhqrnevyabtikgbws5galevtq104up2wxko % 20 zfusgaup4apiaoqadgb5kogbbrqdivcvooabpoowqlchs9nvttgdunrvox5e5opueyyfpkapidbalmjnjffjenaaltgqabueczngaDCW-3kRRoJSgrIxQ3m7MyNkqDCMSzpmRITk4oBZak0%200yRKJBFAkAaNCcqE48kkcmpdTwDRpUAAZN8Oec5NQltdnvhcHzkJw6AQ9gdzk4RHDoFaaCoWDSlg7VPs4wnmDTLsmXU7zh01tdMwYCRGIFGY1B08AWJbrbb7UmLVawDa7RAU2n3Bm662Gx2s2tk72243HWcLoXnPX2ymCVAAPQLqvIHStuRQVxM8gVZCqAhAA" rel="nofollow noreferrer">Playground

下面是一个现代的实现,它也解释了SetMap:

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}


function instantiateEmptyObject(obj: object): object {
if (obj == null) { return {}; }


const prototype = Object.getPrototypeOf(obj);
if (!prototype) {
return {};
}


return Object.create(prototype);
}


function quickCopy(src: object, dest: object): object {
if (dest == null) { return dest; }


return { ...src, ...dest };
}


quickCopy(src, instantiateEmptyObject(new Customer()));

@fenton对选项4的补充,使用angularJS,使用以下代码对对象或数组进行深度复制是相当简单的:

var deepCopy = angular.copy(objectOrArrayToBeCopied)

更多文档可以在这里找到:https://docs.angularjs.org/api/ng/function/angular.copy

如果还想复制方法,而不仅仅是复制数据,请遵循此方法

let copy = new BaseLayer() ;
Object.assign(copy, origin);
copy.x = 8 ; //will not affect the origin object

只需将BaseLayer更改为类名。

我在克隆时使用以下方法。它处理我需要的几乎所有东西,甚至将函数复制到新创建的对象。

  public static clone<T>(value: any) : T {
var o: any = <any>JSON.parse(JSON.stringify(value));
var functions = (<String[]>Object.getOwnPropertyNames(Object.getPrototypeOf(value))).filter(a => a != 'constructor');
for (var i = 0; i < functions.length; i++) {
var name = functions[i].toString();
o[name] = value[name];
}
return <T>o;
}

下面是TypeScript中的deepCopy实现(代码中没有包含any):

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
}