web-dev-qa-db-ja.com

Javascript / Typescriptで配列を複製する

2つのオブジェクトの配列があります。

genericItems: Item[] = [];
backupData: Item[] = [];

HTMLテーブルにgenericItemsdataを入力しています。テーブルは変更可能です。 backUpDataで行ったすべての変更を取り消すためのリセットボタンがあります。この配列にはサービスが入力されます:

getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
  result => {
     this.genericItems = result;
  });
     this.backupData = this.genericItems.slice();
  }

私の考えでは、ユーザーの変更は最初のアレイに反映され、2番目のアレイはリセット操作のバックアップとして使用できます。私がここで直面している問題は、ユーザーがテーブルを変更するときです(genericItems[]) 2番目の配列backupDataも変更されます。

これはどのように起こり、これを防ぐ方法ですか?

39
Arun

これを試して :

配列のクローン:

const myClonedArray  = Object.assign([], myArray);

オブジェクトのクローン:

const myClonedObject = Object.assign({}, myObject);
86
abahet

javascriptのクローン配列とオブジェクトの構文は異なります。遅かれ早かれ誰もが違いを難しい方法で学び、ここで終わることになります。

TypeScriptおよびES6では、配列とオブジェクトにスプレッド演算子を使用できます。

const myClonedArray  = [...myArray];  // This is ok for [1,2,'test','bla']
                                      // But wont work for [{a:1}, {b:2}]. 
                                      // A bug will occur when you 
                                      // modify the clone and you expect the 
                                      // original not to be modified.
                                      // The solution is to do a deep copy
                                      // when you are cloning an array of objects.

オブジェクトのディープコピーを行うには、外部ライブラリが必要です。

import * as cloneDeep from 'lodash/cloneDeep';
const myClonedArray = cloneDeep(myArray);     // This works for [{a:1}, {b:2}]

スプレッド演算子はオブジェクトに対しても機能しますが、浅いコピー(子の最初のレイヤー)のみを行います

const myShallowClonedObject = {...myObject};   // Will do a shallow copy
                                               // and cause you an un expected bug.

オブジェクトのディープコピーを行うには、外部ライブラリが必要です。

 import * as cloneDeep from 'lodash/cloneDeep';
 const deeplyClonedObject = cloneDeep(myObject);   // This works for [{a:{b:2}}]
25
David Dehghan

マップまたは他の同様のソリューションを使用しても、オブジェクトの配列を深く複製するのに役立ちません。新しいライブラリを追加せずにこれを行う簡単な方法は、JSON.stringfyを使用してからJSON.parseを使用することです。

あなたの場合、これはうまくいくはずです:

this.backupData = JSON.parse(JSON.stringify( genericItems));
6
jmuhire

配列を複製する最も簡単な方法は

backUpData = genericItems.concat();

これにより、配列インデックス用の新しいメモリが作成されます

1
VarunJi

PrimeNg DataTableでも同じ問題があります。試して泣いた後、このコードを使用して問題を解決しました。

private deepArrayCopy(arr: SelectItem[]): SelectItem[] {
    const result: SelectItem[] = [];
    if (!arr) {
      return result;
    }
    const arrayLength = arr.length;
    for (let i = 0; i <= arrayLength; i++) {
      const item = arr[i];
      if (item) {
        result.Push({ label: item.label, value: item.value });
      }
    }
    return result;
  }

バックアップ値を初期化するため

backupData = this.deepArrayCopy(genericItems);

変更をリセットするため

genericItems = this.deepArrayCopy(backupData);

魔法の弾丸は、コンストラクターを呼び出す代わりに{}を使用してアイテムを再作成することです。 new SelectItem(item.label, item.value)を試しましたが、うまくいきません。

1
mehdinf

これを試して:

[https://lodash.com/docs/4.17.4#clone][1]

var objects = [{ 'a': 1 }, { 'b': 2 }];

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true
1
Latin Warrior

コードの次の行は、新しい配列を作成し、genericItemsからすべてのオブジェクト参照をその新しい配列にコピーし、backupDataに割り当てます。

this.backupData = this.genericItems.slice();

そのため、backupDatagenericItemsは異なる配列ですが、同じオブジェクト参照が含まれています。

ライブラリを持ち込んで、ディープコピーを行うことができます(@LatinWarriorが述べたように)。

ただし、Itemがあまり複雑でない場合は、cloneメソッドを追加して、オブジェクトを自分でディープクローンすることができます。

class Item {
  somePrimitiveType: string;
  someRefType: any = { someProperty: 0 };

  clone(): Item {
    let clone = new Item();

    // Assignment will copy primitive types

    clone.somePrimitiveType = this.somePrimitiveType;

    // Explicitly deep copy the reference types

    clone.someRefType = {
      someProperty: this.someRefType.someProperty
    };

    return clone;
  }
}

次に、各アイテムでclone()を呼び出します。

this.backupData = this.genericItems.map(item => item.clone());
1
Frank Modica

以下のコードは、最初のレベルのオブジェクトをコピーするのに役立ちます

let original = [{ a: 1 }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))

したがって、以下の場合、値はそのままです

copy[0].a = 23
console.log(original[0].a) //logs 1 -- value didn't change voila :)

この場合は失敗します

let original = [{ a: {b:2} }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
copy[0].a.b = 23;
console.log(original[0].a) //logs 23 -- lost the original one :(

最終アドバイス:

Lodash cloneDeep APIを選択すると、元のオブジェクトから完全に参照解除されたオブジェクト内のオブジェクトをコピーするのに役立ちます。これは、個別のモジュールとしてインストールできます。

ドキュメントを参照:https://github.com/lodash/lodash

個別パッケージhttps://www.npmjs.com/package/lodash.clonedeep

1
Nirus

マップ機能を使用できます

 toArray= fromArray.map(x => x);
0

あなたが望むのは、オブジェクトのディープコピーのようです。 Object.assign()を使用しないのはなぜですか?ライブラリは必要ありません、そしてワンライナー:)

getGenericItems(selected: Item) {
    this.itemService.getGenericItems(selected).subscribe(
        result => {
            this.genericItems = result;
            this.backupDate = Object.assign({}, result); 
            //this.backupdate WILL NOT share the same memory locations as this.genericItems
            //modifying this.genericItems WILL NOT modify this.backupdate
        });
}

Object.assign()の詳細: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

0
CozyAzure

配列のコピーをどこで行っているかを間違えたようです。以下の私の説明と、データを以前の状態にリセットするのに役立つコードにわずかな変更を加えてください。

あなたの例では、次のことが起こっていることがわかります:

  • あなたは一般的なアイテムを取得するリクエストをしている
  • データを取得した後、結果をthis.genericItemsに設定します
  • その直後に、結果としてbackupDataを設定します

3番目のポイントをこの順序で実行したくないと考えるのは正しいですか?

これが良いでしょうか?

  • あなたはデータ要求をします
  • this.genericItemsの最新情報のバックアップコピーを作成する
  • 次に、リクエストの結果としてgenericItemsを設定します

これを試してください:

getGenericItems(selected: Item) {
  this.itemService.getGenericItems(selected).subscribe(
    result => {
       // make a backup before you change the genericItems
       this.backupData = this.genericItems.slice();

       // now update genericItems with the results from your request
       this.genericItems = result;
    });
  }
0
Molik Miah