ChatGPT解决这个技术问题 Extra ChatGPT

打字稿 - 克隆对象

我有一个超类,它是许多子类(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 时出现以下错误:error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.

虽然脚本有效,但我想摆脱编译错误


F
Fenton

解决具体问题

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

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

关闭,transpile 停止使用 typescript 1.3 抱怨,但是一旦在 javascript 中它会抛出错误。 Typescript 1.4.1,不会放过它。
你能澄清一下你是如何使用它的吗?我将其作为对象的方法包含在内,然后出现错误,说不是函数...
我收到以下错误:“错误类型错误:this.constructor(...) 不是构造函数”
你只是从那个客户那里做一个公开的例子吗?
有人可以 TL;DR 对我来说所有答案中给出的哪个解决方案保留了克隆的 OO 类型,即 cloned instanceof MyClass === true
B
BuZZ-dEE

使用扩展运算符 ... 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 的名称字段在层次结构中更接近。
您还将看到 ES2015 和 typescript 开发人员这样做,它们从第一个参数(在我的例子中是一个空的)创建一个对象,并从第二个和后续参数复制属性):let b = Object.assign({}, a);
@KenRimple 你是 100% 正确的,我添加了更多信息。
Object.assign 将为深层对象创建问题。例如 {name: 'x', values: ['a','b','c']}。使用 Object.assign 进行克隆后,两个对象共享值数组,因此更新一个会影响另一个。请参阅:developer.mozilla.org/en/docs/Web/JavaScript/Reference/…(“深度克隆警告”部分)。它说:对于深度克隆,我们需要使用其他替代方法。这是因为当被分配的属性是对象时, Object.assign() 会复制属性引用。
L
Lars

尝试这个:

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

这没关系,但您应该记住,在序列化/解析时,您将丢失原型信息和 json 不支持的所有类型。
此外,与提供的 deepCopy 函数 above 相比,这似乎效率较低。
当我使用“(JSON.parse(JSON.stringify(objectToCopy)));”时出现此错误:“将循环结构转换为 JSON”
仅适用于 98% 的情况。至少会导致缺少具有 undefined 值的键。如果 objectToCopy = { x : undefined}; 则在运行您的代码后 Object.keys(objectToCopy).length1,而 Object.keys(copy).length0
P
Pang

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

let shallowClone = { ...original };

H
Homer

使用 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


注意:这将创建一个浅拷贝
P
Polv

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

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

这可以改变道具的顺序。只是对某些人的警告。它也不能正确处理日期。
这可以改变道具的顺序——可以尝试 npmjs.com/package/es6-json-stable-stringify 而不是 JSON.stringify
@Polv,如果有人依赖对象中键的顺序,我认为他们的问题比 clone 更大。 :)
此解决方案可能会丢失具有 undefined 值的键。请参阅我对上述类似答案的评论:stackoverflow.com/questions/28150967/typescript-cloning-object/…
我确实明确地说“可序列化”。此外,它确实取决于用例,但我总是很乐意扔掉未定义的(我知道这在数组中是不可能的)。对于日期和正则表达式,或者更多(例如大多数类、大多数函数),我建议使用递归函数 -- stackoverflow.com/questions/122102/…
u
user2878850

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

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)

我只是想知道是否可以使用库,如果你真的不知道/理解实现/含义? (cloneDeep 的实现是 github.com/lodash/lodash/blob/master/.internal/baseClone.js)我认为涉及不可枚举属性的递归函数是最好的解决方案之一。 (在 this QA 中的某处。)
M
Muhammad Ali

我的看法:

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。这很可能不是您要的!
@Aidin那么如何确保深拷贝?
此问题中的任何其他解决方案,即递归地按值复制(例如,marckassay 的 stackoverflow.com/a/53025968)确保这一点,因为没有对目标对象中维护的源对象的引用。
D
Decade Moon

你也可以有这样的东西:

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 的返回类型将始终与实例的类型匹配。


D
Dylan Watson

如果您收到此错误:

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();
M
Mauricio Gracia Gutierrez

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

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

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


m
marckassay

这是我的混搭!这是一个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')。否则你的空值将变成一个空对象。
A
Augustin R

您可以将 destructuring assignmentspread syntax 一起使用:

var obj = {id = 1, name = 'product1'};
var clonedObject = {...obj};

虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高答案的长期价值。
V
Valeriy Katkov

自 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


UncopiableUser// compile time error 总是很好,但它在递归函数解决方案中的应用效果如何?
T
Timur Osadchiy

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

K
Keith Stein

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

Z
ZiiMakc

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

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)

b
blid

这是一个现代实现,它也解释了 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}


F
Ferhatos

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

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

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

希望有帮助


可能会错过具有 undefined 值的键。请参阅我对上述类似答案的评论:stackoverflow.com/questions/28150967/typescript-cloning-object/…
B
Bernoulli IT

我最终做了:

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


a
alehro

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

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

这个问题没有标记 JQuery,也没有在问题中提到 JQuery。将 JQuery 包含在项目中只是为了进行深度克隆也将是巨大的开销。
这很公平,但 OP 不是关于如何克隆,而是关于识别他提供的代码中的问题,并且您在没有真正回答问题的情况下用 jQuery 克隆方式做出了回应。我不是反对你的人,但我相信这可能就是你被反对的原因。
p
patrickbadley

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

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

C
Cvassend

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

var deepCopy = angular.copy(objectOrArrayToBeCopied)

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


u
user1931270

克隆时我使用以下内容。它处理我需要的大部分内容,甚至将函数复制到新创建的对象。

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

P
Pradeet Swamy
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()));

目前状态下的这个答案并没有那么有用。您能否添加有关如何使用它来解决原始问题的更多详细信息?
L
LosManos

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

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

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


功能?数组?日期对象?保留类型?当然,对象呢?如果上述函数遇到上述任何一种类型,它将无法进行深度克隆。您将复制对相同数据的引用。当他们去编辑克隆对象的子属性时,他们最终也会编辑原始对象。