машинопись - клонирование объекта

97

У меня есть суперкласс, который является родительским (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 работает, , я хотел бы избавиться от переполненной ошибки

13 ответов

112
Лучший ответ

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

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

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

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

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

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

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
  • 0
    Близко, конвейер перестал жаловаться с машинописью 1.3, но однажды в javascript он выдаст ошибку. Машинопись 1.4.1, не отпустит.
  • 0
    Вы можете использовать дженерики, чтобы получить результат того же типа, что и клонируемый.
Показать ещё 2 комментария
125

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" не отражает копию.

  • 4
    Я не думаю, что это совершенно правильно. Object.create(obj1) создает новый объект и назначает obj1 в качестве прототипа. Ни одно из полей в obj1 не копируется и не клонируется. Таким образом, изменения в obj1 без изменения obj2 будут видны, так как он по существу не имеет свойств. Если вы сначала измените obj2, то прототип не будет виден для поля, которое вы определяете, так как поле obj2 с именем находится ближе в иерархии.
  • 2
    Вы также увидите, что ES2015 и разработчики машинописного текста делают это вместо этого, что создает объект из 1-го параметра (в моем случае пустой) и копирует свойства из второго и последующих параметров): let b = Object.assign({}, a);
Показать ещё 3 комментария
27

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

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)));
}
  • 1
    Это нормально, но вы должны помнить, что при сериализации / разборе вы потеряете информацию о прототипах и все типы, не поддерживаемые в json.
  • 0
    Также это кажется менее эффективным по сравнению с функцией deepCopy, представленной выше .
Показать ещё 1 комментарий
18

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

let shallowClone = { ...original };
14

Легко получить мелкую копию с помощью "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

  • 2
    Примечание: это создаст мелкую копию
4

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

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 всегда будет соответствовать типу экземпляра.

2

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

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

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

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

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

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

Сам столкнулся с этой проблемой и в конце написал небольшую библиотеку 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    
1

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

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

Это правильный script:

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;
}
  • 2
    Правильно cloneObj[attribut] = this.clone(); ? или вы имеете в виду cloneObj[attribut] = this[attribut].clone();
0

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

0

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

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

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

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

0

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню