Подтвердить что ты не робот

Typescript - объект клонирования

У меня есть суперкласс, который является родительским (Entity) для многих подклассов (Customer, Product, ProductCategory...)

Я ищу, чтобы динамически клонировать объект, который содержит разные вспомогательные объекты в Typescript.

В примере: a Customer, у которого есть другой Product, у которого есть 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.

Хотя script работает, , я хотел бы избавиться от переполненной ошибки

4b9b3361

Ответ 1

Решение конкретной проблемы

Вы можете использовать утверждение типа, чтобы сообщить компилятору, что вы знаете лучше:

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

Клонирование

Имейте в виду, что иногда лучше написать собственное отображение, а не быть полностью динамичным. Однако есть несколько приемов "клонирования", которые вы можете использовать для получения разностных эффектов.

Я буду использовать следующий код для всех последующих примеров:

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

Ответ 2

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. Комбинированное поле "комплекс" также было скопировано в obj2. Мутационное поле "complex" не отражает копию.

Ответ 3

Попробуйте следующее:

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

Ответ 4

Typescript/Javascript имеет свой собственный оператор для мелкого клонирования:

let shallowClone = { ...original };

Ответ 5

Легко получить мелкую копию с помощью "Object Spread", представленной в TypeScript 2.1

this 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

Ответ 6

Для сериализуемого глубокого клона, с информацией о типе,

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

Ответ 7

У вас также может быть что-то вроде этого:

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, иначе вы получите частичные клоны.

Тип возврата this всегда будет соответствовать типу экземпляра.

Ответ 8

Мой взгляд на это:

Object.assign(...) только копирует свойства, и мы теряем прототип и методы.

Object.create(...) не копирует свойства для меня, а просто создает прототип.

Для меня сработало создание прототипа с использованием Object.create(...) и копирование свойств в него с помощью Object.assign(...):

Так что для объекта foo, сделайте клон следующим образом:

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

Ответ 9

Если вы получили эту ошибку:

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

Ответ 10

Сам столкнулся с этой проблемой и в конце написал небольшую библиотеку cloneable-ts, которая предоставляет абстрактный класс, который добавляет метод клонирования к любому классу, расширяющему его. Абстрактный класс заимствует функцию глубокого копирования, описанную в принятом ответе Фентоном, только заменяя 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    

Ответ 11

Для простого клонирования содержимого объекта дыры я просто строгую и разбираю экземпляр:

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

В то время как я изменяю данные в дереве objectToClone, в cloneObject нет изменений. Это было моим требованием.

Надеюсь, что это поможет

Ответ 12

Вот мое пюре! И вот ссылка на 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;
    };

Ответ 13

Добавьте "lodash.clonedeep": "^4.5.0" в ваш package.json. Тогда используйте как это:

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)

Ответ 14

Я закончил тем, что сделал:

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

Ответ 15

Как насчет старого доброго jQuery?! Вот глубокий клон:

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

Ответ 16

Я попытался создать универсальный сервис копирования/клонирования, который сохраняет типы для вложенных объектов. Хотелось бы получить обратную связь, если я делаю что-то не так, но, кажется, до сих пор работает...

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

@Injectable()
export class CopyService {

  public deepCopy<T>(objectToClone: T): T {
    // If it 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;
    }
  }
}

Ответ 17

  public clone(toClone: any): any {
    const newClone = {};
    for (const attribut in toClone) {
      if (typeof toClone[attribut] === 'object') {
        newClone[attribut] = this.clone(toClone[attribut]);
      } else {
        newClone[attribut] = toClone[attribut];
      }
    }
    return newClone;
  }

Ответ 18

В 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

Я просто строю объект и сохраняю его в переменной, которая снова обрабатывается по мере необходимости.

Ответ 20

Если у вас уже есть целевой объект, поэтому вы не хотите создавать его заново (например, при обновлении массива), вам необходимо скопировать свойства.
Если вы сделали это следующим образом:

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

Хвала должна. (посмотрите на заголовок "версия 2")