Клонирование массива в Javascript/Typescript

У меня есть массив из двух объектов:

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

Я заполняю свою таблицу HTML данными genericItems. Таблица является модифицируемой. Есть кнопка сброса, чтобы отменить все изменения, сделанные с помощью backUpData. Этот массив заполняется службой:

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

Моя идея заключалась в том, что пользовательские изменения будут отражены в первом массиве, а второй массив можно использовать в качестве резервной копии для операции сброса. Проблема, с которой я здесь сталкиваюсь, заключается в том, что пользователь изменяет таблицу (genericItems[]), второй массив backupData также изменяется).

Как это происходит и как это предотвратить?

Ответ 1

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

Клонировать массив:

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

Клонировать объект:

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

Ответ 2

Клонирование массивов и объектов в 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}}]

Ответ 3

Использование карты или другого подобного решения не поможет глубоко клонировать массив объектов. Более простой способ сделать это без добавления новой библиотеки - использовать JSON.stringfy, а затем JSON.parse.

В вашем случае это должно работать:

this.backupData = JSON.parse(JSON.stringify(genericItems));
  • Используя последнюю версию JS/TS, установка большой библиотеки, такой как lodash, для одной или двух функций - плохой шаг. Вы оцените производительность своего приложения, и в конечном итоге вам придется поддерживать обновления библиотеки! проверить https://bundlephobia.com/[email protected]
  • Для маленьких объектов cloneDeep может быть быстрее, но для более крупных и глубоких объектов клон json становится быстрее. Так что в этом случае вы не должны стесняться использовать его. проверьте https://www.measurethat.net/Benchmarks/Show/6039/0/lodash-clonedeep-vs-json-clone-larger-object и информацию https://v8.dev/blog/cost-of-javascript-2019#json

  • Неудобно, что ваш исходный объект должен быть преобразован в JSON.

Ответ 4

самый простой способ клонировать массив - это

backUpData = genericItems.concat();

Это создаст новую память для индексов массива

Ответ 5

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

[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

Ответ 6

Следующая строка вашего кода создает новый массив, копирует все ссылки на объекты из genericItems в этот новый массив и назначает его backupData:

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

Итак, в то время как backupData и genericItems - разные массивы, они содержат те же точные ссылки на объекты.

Вы можете принести библиотеку для глубокого копирования для вас (как упоминал @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());

Ответ 7

Ниже код может помочь вам скопировать объекты первого уровня

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

Ответ 8

У меня такая же проблема с параметром 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), который не работает.

Ответ 9

Похоже, вы ошиблись в том, где вы делаете копию массива. Взгляните на мое объяснение ниже и небольшую модификацию кода, который должен работать, помогая вам reset данные в предыдущее состояние.

В вашем примере я вижу следующее:

  • вы делаете запрос на получение общих элементов
  • после того, как вы получите данные, которые вы установите для результатов 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;
    });
  }

Ответ 10

Похоже, что вы хотите Deep Copy объекта. Почему бы не использовать Object.assign()? Никаких libaries не нужно, и его однострочный:)

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

Ответ 11

Вы можете использовать функцию карты

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

Ответ 12

Если ваши элементы в массиве не являются примитивными, вы можете использовать для этого оператор распространения.

this.plansCopy = this.plans.map(obj => ({...obj}));

Полный ответ: fooobar.com/questions/14831200/...

Ответ 13

Попробуй это

const returnedTarget = Object.assign(target, source);

и передать пустой массив цели

если сложные объекты у меня так работают

$.extend(true, [], originalArray) в случае массива

$.extend(true, {}, originalObject) в случае объекта