Я пытаюсь изменить один элемент из массива, элементы которого ранее дублировались н раз. Чтобы выполнить дублирование массива, я просто использовал пользовательскую функцию duplicateElements(array, times)
из сообщения это (см. ответ @Bamieh). Как показано в примере ниже, проблема в том, что я не могу изменить один элемент массива без изменения других элементов:
function duplicateElements(array, times) {
return array.reduce((res, current) => {
return res.concat(Array(times).fill(current));
}, []);
}
var myvar = duplicateElements([{ a: 1 }, { a: 2 }], 2);
myvar[0].a = 3;
console.info(myvar);
// (4) [{…}, {…}, {…}, {…}]
// 0: {a: 3}
// 1: {a: 3}
// 2: {a: 2}
// 3: {a: 2}
// length: 4
Как вы можете видеть, myvar[1].a
также был изменен, хотя это и не предполагалось. Как я могу избежать этой проблемы?
@Shidersz Я пробовал _.cloneDeep(current)
из lodash, но это не сработало.
Как это не сработало, обновите код, который вы пробовали, я только что прочитал об этом методе из Lodash
, просто из любопытства, и он отлично работал в некоторых тестах, которые я сделал. Если вы хотите использовать Lodash
, я могу показать вам пример.
Заменять
Array(times).fill(current)
который добавит одну ссылку на current
несколько раз в массив с помощью:
Array.from({ length: times }, () => ({...current }))
который будет мелким клоном current
. Обратите внимание, что тогда код будет работать только с объектами, а не с примитивами.
Я бы сделал:
const duplicateElements = (array, length) =>
array.flatMap(current => Array.from({ length }, () => ({ ...current }));
Ваша пользовательская функция (const...
) отлично работает с моим примером, но не с duplicateElements(["aa", "bb"], 2)
, где ожидаемый результат должен быть ["aa", "aa", "bb", "bb"]
. Есть ли способ заставить его работать в обоих случаях?
@mat, вам нужен какой-то способ глубокого клонирования, вы найдете несколько реализаций, если будете его искать.
Проблема в том, что вы передаете ссылку на исходный объект в Array(times).fill(current) .
В этом случае две копии первого {a:2} являются одной и той же копией оригинала (они ссылаются на одно и то же пространство в памяти), поэтому, если вы измените одну, две из них изменятся, поскольку они ссылаются на один и тот же объект в Память.
Вы должны сделать функцию глубокого клонирования или, возможно, распространить объект внутри нового. Вы можете изменить исходную функцию для работы с объектами и примитивами следующим образом:
function duplicateElements(elementsArray, times) {
//Make a new placeholder array
var newArray = [];
//Loop the array of elements you want to duplicate
for (let index = 0; index < elementsArray.length; index++) {
//Current element of the array of element
var currentElement = elementsArray[index];
//Current type of the element to check if it is an object or not
var currentType = typeof currentElement
//Loop over the times you want to copy the element
for (let index = 0; index < times; index++) {
//If the new element is not an object
if (currentType !== "object" && currentType){
//append the element
newArray.push(currentElement)
//if it is an Object
} else if (currentType === "object" && currentType){
//append an spreaded new Object https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
newArray.push({...currentElement})
}
}
}
return newArray;
}
Это не оптимальный способ сделать это, но я думаю, что, возможно, вы новичок в javascript и вам лучше изучить старый способ зацикливания, прежде чем использовать дополнительные функции массива (как ответ Джонаса Уилмса, это также хороший ответ ).
Я бы порекомендовал javascript.info и красноречивый javascript, чтобы узнать больше о языке
Основная причина этого, как указано в документации Заполнить массив, заключается в том, что при работе с объектами он будет копировать по ссылке:
When fill gets passed an object, it will copy the reference and fill the array with references to that object.
С lodash (и _.cloneDeep
) это одна строка:
let dubFn = (arr, t=1) =>
_.concat(arr, _.flatMap(_.times(t, 0), x => _.cloneDeep(arr)))
let r1 = dubFn([{a:1},{b:3}]) // no parameter would mean just 1 dub
let r2 = dubFn([{a:1},{b:3},5,[1]], 2) // 2 dublicates
r1[0].a = 3
r2[0].a = 3
console.info(r1)
console.info(r2)
<script src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
Обратите внимание, что теперь это работает с массивы/объекты и примитивы.
Идея состоит в том, чтобы использовать _.concat
для возврата новой конкатенированной версии входного массива с комбинацией нескольких функций, которые в конце возвращают массив клонированных объектов. Мы используем _.times
для возврата массива в данном случае t
элементов, а затем для каждого из этих элементов мы заменяем deep clone
массива. _.flatMap
необходим для выравнивания конечного результата, так как мы получаем массив массивов после вызова _.times
.
С ES6 вы можете сделать что-то вроде этого:
let dubElements = (arr, t) =>
[...arr, ...new Array(t).fill().flatMap(x => arr.map(y => ({...y})))]
let r1 = dubElements([{a:1},{b:3}])
let r2 = dubElements([{a:1},{b:3}],2)
r1[0].a = 3
r2[0].a = 3
console.info(r1)
console.info(r2)
Где мы объединяем массивы с помощью оператора распространения и используем new Array(t)
для создания нового массива дубликатов и убеждаемся, что мы fill
его с помощью undefined
в этом случае, после чего мы flatMap
результаты (которые мы снова отображаем через clone
с помощью оператора распространения.
Обратите внимание, что это работает for your use case specifically
. Если вы хотите сделать его более общим, вам нужно больше расширить последнюю функцию карты и т. д.
Если вы хотите сохранить порядок элементов, вы можете сделать что-то вроде этого:
let dubElements = (arr, t=1) => {
let _result = []
arr.forEach(x => {
for(let i=0; i<t+1; i++) {
_result.push({...x})
}
})
return _result
}
let result = dubElements([{a:1},{b:3}],2)
result[0].a = 3
console.info(result)
Есть ли способ сохранить порядок элементов (например, ["a", "a", "b", "b"], вместо ["a", "b", "a", "b"] )
Обновил ответ методом сохранения порядка.
Если ваши объекты могут иметь несколько уровней вложенности, вам придется искать информацию о глубоком клонировании объекта, поэтому вы можете сделать что-то вроде:
res.concat(Array(times).fill(clone(current)));
. В противном случае вы можете использовать синтаксис распространения, например:res.concat(Array(times).fill({...current}))