web-dev-qa-db-ja.com

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

オブジェクトのツリー全体を複製するために、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.

スクリプトは動作しますが、変換されたエラーを取り除きたいです

114
David Laberge

特定の問題を解決する

型アサーションを使用して、知っていることをコンパイラに伝えることができます。

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
154
Fenton

1.スプレッド演算子を使用

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

スプレッド演算子は、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を使用すると、プロトタイプで宣言された関数はすべて、新しく作成されたオブジェクトで使用できるようになります。


シャローコピーに関することはほとんどありません

シャローコピーでは、古いオブジェクトのすべてのフィールドが新しいオブジェクトに追加されますが、元のオブジェクトに複合型のフィールド(オブジェクト、配列など)がある場合、それらのフィールドは同じ参照で新しいオブジェクトに追加されます。元のオブジェクト内のそのようなフィールドの突然変異は新しいオブジェクトに反映されます。

落とし穴のように見えますが、複雑なオブジェクト全体をコピーする必要がある状況はまれです。シャローコピーはメモリの大部分を再利用するため、ディープコピーに比べて非常に安価です。


ディープコピー

スプレッド演算子は、ディープコピーに便利です。

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

上記のコードはobj1のディープコピーを作成しました。複合フィールド "complex"もobj2にコピーされました。突然変異フィールド "complex"はコピーを反映しません。

137
Maciej Sikora

これを試して:

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

TypeScript/Javascriptには、シャロークローニングのための独自の演算子があります。

let shallowClone = { ...original };
21
Luca C.

TypeScript 2.1で導入された "Object Spread"を使えば、簡単なコピーを手に入れることができます。

この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

14
Homer

次のようなものもあります。

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の戻り値の型は常にインスタンスの型と一致します。

5
Decade Moon

型情報を持つシリアル化可能なディープクローンの場合、

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

私自身がこの問題に出会い、最後に抽象クラスを提供する小さなライブラリ cloneable-ts を書きました。 。抽象クラスは、元のオブジェクトのクラスを保持するために、copy = {};copy = Object.create(originalObj)に置き換えただけで、Fentonによる受け入れられた回答で説明されているディープコピー機能を借用します。これがクラスの使用例です。

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    
2
Tim Osadchiy

私の考え

Object.assign(...)はプロパティをコピーするだけで、プロトタイプとメソッドを失います。

Object.create(...)は私のためにプロパティをコピーするのではなく、ただプロトタイプを作成するだけです。

私にとってうまくいったのは、Object.create(...)を使ってプロトタイプを作成し、Object.assign(...)を使ってそれにプロパティをコピーすることです。

オブジェクトfooに対しては、cloneを次のようにします。

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

これが私のマッシュアップです!そしてこれが 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;
    };
1
marckassay

ホールオブジェクトの内容の簡単な複製として、インスタンスを文字列化して解析するだけです。

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

ObjectToCloneツリーのデータを変更しても、cloneObjectには変更はありません。それが私の要求でした。

それが助けを願っています

1
Ferhatos

このエラーが発生した場合:

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

入れ子になったオブジェクトの型を保持する汎用のコピー/クローンサービスを作成するのに苦労しました。私が何か悪いことをしているのならフィードバックが大好きですが、それは今のところうまくいくようです...

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

古き良きjQueryはどうですか。これがディープクローンです。

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

私はやってしまった:

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のランタイムエラーが発生しました。

TypeScriptのバージョン:2.4.2

0
Bernoulli IT

"lodash.clonedeep": "^4.5.0"package.jsonに追加してください。その後、このように使用してください:

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)
0
user2878850