私は多くのサブクラス(Entity
、Customer
、Product
...)の親(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.
スクリプトは動作しますが、変換されたエラーを取り除きたいです
型アサーションを使用して、知っていることをコンパイラに伝えることができます。
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
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"はコピーを反映しません。
これを試して:
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)));
}
TypeScript/Javascriptには、シャロークローニングのための独自の演算子があります。
let shallowClone = { ...original };
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
次のようなものもあります。
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
の戻り値の型は常にインスタンスの型と一致します。
型情報を持つシリアル化可能なディープクローンの場合、
export function clone<T>(a: T): T {
return JSON.parse(JSON.stringify(a));
}
私自身がこの問題に出会い、最後に抽象クラスを提供する小さなライブラリ 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
私の考え
Object.assign(...)
はプロパティをコピーするだけで、プロトタイプとメソッドを失います。
Object.create(...)
は私のためにプロパティをコピーするのではなく、ただプロトタイプを作成するだけです。
私にとってうまくいったのは、Object.create(...)
を使ってプロトタイプを作成し、Object.assign(...)
を使ってそれにプロパティをコピーすることです。
オブジェクトfoo
に対しては、cloneを次のようにします。
Object.assign(Object.create(foo), foo)
これが私のマッシュアップです!そしてこれが 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;
};
ホールオブジェクトの内容の簡単な複製として、インスタンスを文字列化して解析するだけです。
let cloneObject = JSON.parse(JSON.stringify(objectToClone))
ObjectToCloneツリーのデータを変更しても、cloneObjectには変更はありません。それが私の要求でした。
それが助けを願っています
このエラーが発生した場合:
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;
}
入れ子になったオブジェクトの型を保持する汎用のコピー/クローンサービスを作成するのに苦労しました。私が何か悪いことをしているのならフィードバックが大好きですが、それは今のところうまくいくようです...
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;
}
}
}
古き良きjQueryはどうですか。これがディープクローンです。
var clone = $.extend(true, {}, sourceObject);
私はやってしまった:
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
"lodash.clonedeep": "^4.5.0"
をpackage.json
に追加してください。その後、このように使用してください:
import * as _ from 'lodash';
...
const copy = _.cloneDeep(original)