我有一个超类,它是许多子类(Customer
、Product
、ProductCategory
...)的父类(Entity
)
我正在寻找动态克隆一个包含 Typescript 中不同子对象的对象。
例如:具有不同 Product
的 Customer
具有 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 时出现以下错误:error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.
虽然脚本有效,但我想摆脱编译错误
解决具体问题
您可以使用类型断言告诉编译器您更了解:
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)
what kind of thing you can use this on 有一些限制。
请记住,有时最好编写自己的映射 - 而不是完全动态的。但是,您可以使用一些“克隆”技巧来获得不同的效果。
我将在所有后续示例中使用以下代码:
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
使用扩展运算符 ... const obj1 = { param: "value" };常量 obj2 = { ...obj1 };
传播运算符从 obj1 获取所有字段并将它们传播到 obj2。结果,您将获得具有新引用的新对象以及与原始对象相同的字段。
请记住,它是浅拷贝,这意味着如果对象是嵌套的,则其嵌套的复合参数将通过相同的引用存在于新对象中。
Object.assign() const obj1={ 参数:“值” };常量 obj2:any = Object.assign({}, obj1);
Object.assign 创建真正的副本,但只有自己的属性,所以原型中的属性不会存在于复制的对象中。它也是浅拷贝。
Object.create() const obj1={ 参数:“值” };常量 obj2:any = Object.create(obj1);
Object.create
不是在进行真正的克隆,它是从原型创建对象。因此,如果对象应该克隆主要类型属性,请使用它,因为主要类型属性分配不是通过引用完成的。
Object.create 的优点是在原型中声明的任何函数都将在我们新创建的对象中可用。
关于浅拷贝的几件事
浅拷贝将旧对象的所有字段放入新对象中,但这也意味着如果原始对象具有复合类型字段(对象、数组等),那么这些字段将放入具有相同引用的新对象中。原始对象中的这种字段的突变将反映在新对象中。
这可能看起来像一个陷阱,但真正需要复制整个复杂对象的情况很少见。浅拷贝将重用大部分内存,这意味着与深拷贝相比非常便宜。
深拷贝
扩展运算符可以方便地进行深度复制。
const obj1 = { param: "value", complex: { name: "John"}}
const obj2 = { ...obj1, complex: {...obj1.complex}};
上面的代码创建了 obj1 的深层副本。复合字段“complex”也被复制到 obj2 中。突变字段“复杂”不会反映副本。
Object.create(obj1)
创建一个新对象并将 obj1 指定为原型。 obj1 中的所有字段都不会被复制或克隆。所以会看到 obj1 上的更改而不修改 obj2,因为它本质上没有属性。如果您先修改 obj2,则不会看到您定义的字段的原型,因为 obj2 的名称字段在层次结构中更接近。
let b = Object.assign({}, a);
尝试这个:
let copy = (JSON.parse(JSON.stringify(objectToCopy)));
在您使用非常大的对象或您的对象具有不可序列化的属性之前,这是一个很好的解决方案。
为了保持类型安全,您可以在要从中复制的类中使用复制功能:
getCopy(): YourClassName{
return (JSON.parse(JSON.stringify(this)));
}
或以静态方式:
static createCopy(objectToCopy: YourClassName): YourClassName{
return (JSON.parse(JSON.stringify(objectToCopy)));
}
undefined
值的键。如果 objectToCopy = { x : undefined};
则在运行您的代码后 Object.keys(objectToCopy).length
是 1
,而 Object.keys(copy).length
是 0
。
TypeScript/JavaScript 有自己的浅克隆操作符:
let shallowClone = { ...original };
使用 TypeScript 2.1 中引入的“Object Spread”很容易获得浅拷贝
这个打字稿: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
对于可序列化的深度克隆,类型信息为,
export function clone<T>(a: T): T {
return JSON.parse(JSON.stringify(a));
}
JSON.stringify
clone
更大。 :)
undefined
值的键。请参阅我对上述类似答案的评论:stackoverflow.com/questions/28150967/typescript-cloning-object/…
将 "lodash.clonedeep": "^4.5.0"
添加到您的 package.json
。然后像这样使用:
import * as _ from 'lodash';
...
const copy = _.cloneDeep(original)
我的看法:
Object.assign(...)
只复制属性,我们丢失了原型和方法。
Object.create(...)
不是为我复制属性而只是创建原型。
对我有用的是使用 Object.create(...)
创建原型并使用 Object.assign(...)
将属性复制到其中:
因此,对于对象 foo
,请像这样进行克隆:
Object.assign(Object.create(foo), foo)
foo
成为 clonedFoo
(新对象)的原型父级。虽然这听起来不错,但您应该记住,将在原型链中查找缺失的属性,因此 const a = { x: 8 }; const c = Object.assign(Object.create(a), a); delete c.x; console.log(c.x);
打印出 8,而应该是 undefined
! (REPL 链接:repl.it/repls/CompetitivePreemptiveKeygen)
foo
,它将自动显示在 clonedFoo
!例如 foo.y = 9; console.log(clonedFoo.y)
将打印出 9
而不是 undefined
。这很可能不是您要的!
你也可以有这样的东西:
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
的返回类型将始终与实例的类型匹配。
如果您收到此错误:
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;
}
cloneObj[attribut] = this.clone();
?或者你的意思是cloneObj[attribut] = this[attribut].clone();
如果您还想复制方法,而不仅仅是数据,请遵循此方法
let copy = new BaseLayer() ;
Object.assign(copy, origin);
copy.x = 8 ; //will not affect the origin object
只需将 BaseLayer
更改为您的构造函数的名称。
这是我的混搭!这是一个StackBlitz link。它目前仅限于复制简单类型和对象类型,但我认为可以轻松修改。
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;
};
typeof null
也是一个对象,因此查询应该是 if (source[P] !== null && typeof source[P] === 'object')
。否则你的空值将变成一个空对象。
您可以将 destructuring assignment 与 spread syntax 一起使用:
var obj = {id = 1, name = 'product1'};
var clonedObject = {...obj};
自 TypeScript 3.7 发布以来,现在支持 recursive type aliases,它允许我们定义类型安全的 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
UncopiableUser
的 // compile time error
总是很好,但它在递归函数解决方案中的应用效果如何?
我自己遇到了这个问题,最后写了一个小库cloneable-ts,它提供了一个抽象类,它为任何扩展它的类添加了一个克隆方法。抽象类借用了 Fenton 接受的答案中描述的深度复制函数,仅将 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
辅助方法:
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 中,我用 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)
这是一个现代实现,它也解释了 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}
对于洞对象内容的简单克隆,我只是对实例进行字符串化和解析:
let cloneObject = JSON.parse(JSON.stringify(objectToClone))
虽然我更改了 objectToClone 树中的数据,但 cloneObject 没有变化。那是我的要求。
希望有帮助
undefined
值的键。请参阅我对上述类似答案的评论:stackoverflow.com/questions/28150967/typescript-cloning-object/…
我最终做了:
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 给出了运行时错误。
打字稿版本:2.4.2
好的旧 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;
}
}
}
@fenton 对选项 4 的补充,使用 angularJS 使用以下代码对对象或数组进行深层复制相当简单:
var deepCopy = angular.copy(objectOrArrayToBeCopied)
可在此处找到更多文档:https://docs.angularjs.org/api/ng/function/angular.copy
克隆时我使用以下内容。它处理我需要的大部分内容,甚至将函数复制到新创建的对象。
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;
}
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()));
如果您已经拥有目标对象,那么您不想重新创建它(例如更新数组),您必须复制属性。如果这样做了:
Object.keys(source).forEach((key) => {
copy[key] = source[key]
})
Praise is due. (look at headline "version 2")
cloned instanceof MyClass === true
?