打字稿 - 克隆对象

时间:2015-01-26 13:25:23

标签: javascript typescript

我有一个超级类,它是许多子类(EntityCustomerProduct的父级(ProductCategory)...)

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

例如:Customer Product ProductCategory var cust:Customer = new Customer (); cust.name = "someName"; cust.products.push(new Product(someId1)); cust.products.push(new Product(someId2)); <{1}}

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

error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.将以下错误转换为javascript:{{1}}

虽然脚本有效, 我想摆脱已发现的错误

25 个答案:

答案 0 :(得分:151)

解决特定问题

您可以使用类型断言告诉编译器您更了解:

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

克隆

请记住,有时候编写自己的映射会更好 - 而不是完全动态。但是,有一些&#34;克隆&#34;你可以使用的技巧给你带来不同的效果。

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

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

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

alert(customer.name + ' ' + customer.example.type); // David 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 = <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 :(得分:135)

1.使用传播运营商

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

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

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

2.Object.assign()

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

Object.assign 创建真实副本,但只创建自己的属性,因此原型中的属性不会存在于复制的对象中。它也是浅色副本。

3.Object.create()

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

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

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

关于浅拷贝的几点事

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

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

深层复制

Spread运算符可以方便进行深层复制。

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

上面的代码创建了obj1的深层副本。复合材料领域&#34;复合材料&#34;也被复制到obj2。变异领域&#34;复杂&#34;不会反映副本。

答案 2 :(得分:32)

试试这个:

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

答案 3 :(得分:20)

Typescript / Javascript有自己的浅层克隆运算符:

let shallowClone = { ...original };

答案 4 :(得分:14)

使用&#34;对象传播&#34;很容易获得浅色副本。在TypeScript 2.1中引入

此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

答案 5 :(得分:9)

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

phpunit.e2e.xml

答案 6 :(得分:5)

你也可以这样:

O(log(n!)) = O(nlog(n))

请确保覆盖所有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; } } 子类中的clone方法,否则您将以部分克隆结束。

Entity的返回类型将始终与实例的类型匹配。

答案 7 :(得分:2)

自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

Playground

答案 8 :(得分:2)

我自己遇到了这个问题,并最终编写了一个小型库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    

答案 9 :(得分:2)

我的看法:

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

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

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

因此,对于对象foo,进行如下克隆:

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

答案 10 :(得分:1)

如果您收到此错误:

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.clone();
        } else {
            cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

答案 11 :(得分:1)

这是一种现代化的实现,它也说明了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}

答案 12 :(得分:1)

这是我的混搭!这里是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;
    };

答案 13 :(得分:1)

对于孔对象内容的简单克隆,我只需对字符串化并解析实例:

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

虽然我在objectToClone树中更改数据,但cloneObject没有变化。这是我的要求。

希望有所帮助

答案 14 :(得分:0)

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

var deepCopy = angular.copy(objectOrArrayToBeCopied)

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

答案 15 :(得分:0)

这将创建一个新对象,然后从另一个对象复制值,这样新对象中的方法就存在了,而不仅仅是数据。

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

只需将 BaseLayer 更改为构造函数的名称即可。

答案 16 :(得分:0)

您可以将destructuring assignmentspread syntax结合使用:

%20

答案 17 :(得分:0)

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

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)

答案 18 :(得分:0)

在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.");
    }

答案 19 :(得分:0)

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

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)

答案 20 :(得分:0)

我努力创建了一个通用的复制/克隆服务,该服务保留嵌套对象的类型。如果我做错了事,很乐意提供反馈,但到目前为止,它似乎仍然有效...

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

答案 21 :(得分:0)

好的旧jQuery怎么样?这是深层克隆:

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

答案 22 :(得分:0)

我最终做了:

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

答案 23 :(得分:-1)

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

答案 24 :(得分:-2)

如果您已经有了目标对象,那么就不想重新创建它(例如更新数组),则必须复制属性。
如果这样做的话:

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

Praise is due. (look at headline "version 2")