关于javascript:typescript – 克隆对象

typescript - cloning object

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

我希望动态克隆一个在typescript中包含不同子对象的对象。

例如:一个Customer有不同的Product谁有一个ProductCategory

1
2
3
4
5
var cust:Customer  = new Customer ();

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

为了克隆整个对象树,我在Entity中创建了一个函数。

1
2
3
4
5
6
7
8
9
10
11
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;
}

当出现在javascript中时,new会增加以下错误:error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.

虽然脚本可以工作,但我想消除所发生的错误


解决具体问题

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

1
2
3
4
5
6
7
8
9
10
11
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;
}

克隆

请记住,有时候编写自己的映射比完全动态映射要好。但是,有一些"克隆"技巧可以让你产生不同的效果。

我将在随后的所有示例中使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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深拷贝:没有

1
2
3
4
5
6
7
8
9
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深拷贝:没有

1
2
3
4
5
6
7
8
9
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

属性:是的方法:是的深拷贝:没有

1
2
3
4
5
6
7
8
9
var clone = Object.create(customer);

alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // OK

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David SteveType

选项4:深度复制功能

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 = <Customer>deepCopy(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


1.使用排列运算符

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

spread运算符从obj1中获取所有字段,并将它们分布在obj2上。在结果中,您将得到具有新引用的新对象以及与原始对象相同的字段。

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

2.对象.assign())

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

object.assign创建真实副本,但只拥有自己的属性,因此原型中的属性将不存在于复制的对象中。它也是肤浅的复制品。

3.对象.创建()

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

Object.create并没有进行真正的克隆,而是从原型中创建对象。因此,如果对象应该克隆主类型属性,请使用它,因为主类型属性分配不是通过引用完成的。

object.create的优点是原型中声明的任何函数都将在新创建的对象中可用。

关于肤浅复制的一些事情

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

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

深拷贝

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

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

上面的代码创建了obj1的深度副本。复合字段"complex"也被复制到obj2中。突变字段"复杂"不会反映拷贝。


试试这个:

1
let copy = (JSON.parse(JSON.stringify(objectToCopy)));

在使用非常大的对象或对象具有不可分割的属性之前,这是一个很好的解决方案。

为了保护类型安全,可以在要从中进行复制的类中使用copy函数:

1
2
3
getCopy(): YourClassName{
    return (JSON.parse(JSON.stringify(this)));
}

或以静态方式:

1
2
3
static createCopy(objectToCopy: YourClassName): YourClassName{
    return (JSON.parse(JSON.stringify(objectToCopy)));
}


typescript/javascript有自己的用于浅克隆的运算符:

1
let shallowClone = { ...original };

使用typescript 2.1中引入的"object spread"很容易得到一个浅拷贝。

这种字体:let copy = { ...original };

生成此javascript:

1
2
3
4
5
6
7
8
9
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


你也可以有这样的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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的返回类型将始终与实例的类型匹配。


我的看法是:

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

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

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

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

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

我自己也遇到了这个问题,最后编写了一个小的库可克隆TS,它提供了一个抽象类,它向扩展它的任何类添加了一个克隆方法。抽象类借用Fenton在接受的答案中描述的深度复制函数,只将copy = {};替换为copy = Object.create(originalObj),以保留原始对象的类。下面是使用类的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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助手方法:

1
2
3
4
5
6
7
8
9
10
11
12
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

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

1
2
3
4
5
6
7
8
9
10
11
   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;
    };


对于Hole对象内容的简单克隆,我只需对实例进行字符串化和解析:

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

虽然我在ObjecttoClone树中更改数据,但CloneObject中没有更改。这是我的请求。

希望有帮助


如果出现此错误:

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

这是正确的脚本:

1
2
3
4
5
6
7
8
9
10
11
public clone(): any {
    var cloneObj = new (this.constructor)(); // line fixed
    for (var attribut in this) {
        if (typeof this[attribut] ==="object") {
            cloneObj[attribut] = this.clone();
        } else {
            cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}


我尝试过创建一个通用的复制/克隆服务,它保留嵌套对象的类型。如果我做错了,我会很乐意得到反馈,但到目前为止,这似乎有效…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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...
object: '
+ JSON.stringify(objectToClone) +"
clone:"
+ 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;
    }
  }
}

好的旧jquery怎么样?!这里是Deep Clone:

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


我最后做了:

1
2
3
4
5
6
7
8
9
10
11
12
13
public clone(): any {
  const result = new (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;
}

因为:

1
var cloneObj = new (this.constructor());

来自@fenton的,给出了运行时错误。

字体脚本版本:2.4.2