Ответ 1
Попробуйте следующее:
Клонировать массив:
const myClonedArray = Object.assign([], myArray);
Клонировать объект:
const myClonedObject = Object.assign({}, myObject);
У меня есть массив из двух объектов:
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
также изменяется).
Как это происходит и как это предотвратить?
Попробуйте следующее:
Клонировать массив:
const myClonedArray = Object.assign([], myArray);
Клонировать объект:
const myClonedObject = Object.assign({}, myObject);
Клонирование массивов и объектов в 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}}]
Использование карты или другого подобного решения не поможет глубоко клонировать массив объектов. Более простой способ сделать это без добавления новой библиотеки - использовать JSON.stringfy, а затем JSON.parse.
В вашем случае это должно работать:
this.backupData = JSON.parse(JSON.stringify(genericItems));
Для маленьких объектов 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.
самый простой способ клонировать массив - это
backUpData = genericItems.concat();
Это создаст новую память для индексов массива
Попробуйте следующее:
[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
Следующая строка вашего кода создает новый массив, копирует все ссылки на объекты из 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());
Ниже код может помочь вам скопировать объекты первого уровня
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
У меня такая же проблема с параметром 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)
, который не работает.
Похоже, вы ошиблись в том, где вы делаете копию массива. Взгляните на мое объяснение ниже и небольшую модификацию кода, который должен работать, помогая вам reset данные в предыдущее состояние.
В вашем примере я вижу следующее:
Как я понял, что вы не хотите, чтобы в этом порядке 3-й пункт?
Будет ли это лучше:
Попробуйте следующее:
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;
});
}
Похоже, что вы хотите 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
Вы можете использовать функцию карты
toArray= fromArray.map(x => x);
Если ваши элементы в массиве не являются примитивными, вы можете использовать для этого оператор распространения.
this.plansCopy = this.plans.map(obj => ({...obj}));
Полный ответ: fooobar.com/questions/14831200/...
const returnedTarget = Object.assign(target, source);
и передать пустой массив цели
если сложные объекты у меня так работают
$.extend(true, [], originalArray)
в случае массива
$.extend(true, {}, originalObject)
в случае объекта