Как дублировать элементы в массиве js без создания «зависимых элементов»?

Я пытаюсь изменить один элемент из массива, элементы которого ранее дублировались н раз. Чтобы выполнить дублирование массива, я просто использовал пользовательскую функцию 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 также был изменен, хотя это и не предполагалось. Как я могу избежать этой проблемы?

Если ваши объекты могут иметь несколько уровней вложенности, вам придется искать информацию о глубоком клонировании объекта, поэтому вы можете сделать что-то вроде: res.concat(Array(times).fill(clone(current)));. В противном случае вы можете использовать синтаксис распространения, например: res.concat(Array(times).fill({...current}))

Shidersz 27.05.2019 19:50

@Shidersz Я пробовал _.cloneDeep(current) из lodash, но это не сработало.

mat 27.05.2019 20:25

Как это не сработало, обновите код, который вы пробовали, я только что прочитал об этом методе из Lodash, просто из любопытства, и он отлично работал в некоторых тестах, которые я сделал. Если вы хотите использовать Lodash, я могу показать вам пример.

Shidersz 27.05.2019 21:19
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
3
141
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Заменять

 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 27.05.2019 20:10

@mat, вам нужен какой-то способ глубокого клонирования, вы найдете несколько реализаций, если будете его искать.

Jonas Wilms 28.05.2019 09:41
Ответ принят как подходящий

Проблема в том, что вы передаете ссылку на исходный объект в 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"] )

mat 28.05.2019 15:22

Обновил ответ методом сохранения порядка.

Akrion 28.05.2019 19:40

Другие вопросы по теме