Установка свойства одного конкретного ключа в объекте в reduce устанавливает его для всех свойств

#javascript #node.js

#javascript #node.js

Вопрос:

Хорошо, я буквально понятия не имею, что здесь происходит. Я предполагаю, что это какая-то проблема со ссылкой? Но я не знаю, как обойти это или что его вызывает.

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

Мне нужно выполнить итерацию по этому списку объектов, и, используя timeframeId в объекте метаданных и идентификатор в объекте данных, я хочу присвоить весь объект данных соответствующей иерархии timeframeId и id в предварительно заполненном объекте.

По какой-то причине все data свойства перезаписываются до любой последней строки data .

Я связал repl, чтобы вы могли убедиться сами: https://repl.it/@ThomasVermeers1/UnwrittenNoisyFirm#index.js

Но мой код выглядит следующим образом:

 const buildSegmentsFromRows = (rows, timeframeMetadata, defaultSegmentData) => {
  // Prepopulate object to make sure every timeframe has a 'hello' key in it with some data
  const emptySegments = timeframeMetadata.reduce((segmentMap, metadata) => {
    segmentMap[metadata.timeframeId] = {
      metadata,
      segments: defaultSegmentData,
    };
    return segmentMap;
  }, {});

  // Now simply just loop over the rows, and set [row.metadata.timeframeId].segments[row.data.id] to row.data
  const segments = rows.reduce((partialSegments, row) => {
    const { timeframeId } = row.metadata;
    const { id } = row.data;

    /**
     * This is the line where everything goes wrong
     */
    partialSegments[timeframeId].segments[id] = row.data;
    return partialSegments;
  }, emptySegments);
  return segments;
};

const rows = [
  {
    metadata: { timeframeId: '20202_01' },
    data: {
      'id': 'hello', 'value': 15
    }
  },
  {
    metadata: { timeframeId: '20202_02' },
    data: {
      'id': 'hello', 'value': 10
    }
  }
]

const timeframemetadata = [
  { timeframeId: '20202_01'},
  { timeframeId: '20202_02'}
]

const defaultSegmentData = {
  'hello': {
    'id': 'hello',
  }
}
console.log(JSON.stringify(buildSegmentsFromRows(rows, timeframemetadata, defaultSegmentData), null, 2))

 

Я ожидаю, что конечный результат будет:

 {
  "20202_01": {
    "metadata": {
      "timeframeId": "20202_01"
    },
    "segments": {
      "hello": {
        "id": "hello",
        "value": 15
      }
    }
  },
  "20202_02": {
    "metadata": {
      "timeframeId": "20202_02"
    },
    "segments": {
      "hello": {
        "id": "hello",
        "value": 10
      }
    }
  }
}
 

Но вместо value этого устанавливается значение 10 во всех экземплярах. Я думаю, это потому, что мы устанавливаем свойство row.data , которое является ссылкой и обновляется при каждом вызове? Но я здесь в полной растерянности.

Комментарии:

1. На самом деле я не уверен, нужно ли это делать без ссылки, потому что, если вы даже просто замените partialSegments[timeframeId].segments[id] = row.data; на partialSegments[timeframeId].segments[id] = Math.random(); все значения, для них будет установлено одинаковое случайное число.

2. Вероятно, вы пытаетесь повторно использовать объект, а не создавать совершенно новый где-то в своем коде. И, таким образом, вы получаете ссылки на один и тот же объект по всей вашей структуре.

Ответ №1:

Проблема в том, что вы ссылаетесь на один и тот же объект для каждого segments в списке.

Поэтому изменение значения segments[id] будет обновляться defaultSegmentData , в результате чего каждая ссылка на defaultSegmentData также изменится.

   const emptySegments = timeframeMetadata.reduce((segmentMap, metadata) => {
    segmentMap[metadata.timeframeId] = {
      metadata,
      segments: defaultSegmentData, // Everything goes wrong here.
    };
    return segmentMap;
  }, {});
 

Простое решение этой проблемы — избегать использования одной и той же ссылки на объект при создании segmentMap :

   const emptySegments = timeframeMetadata.reduce((segmentMap, metadata) => {
    segmentMap[metadata.timeframeId] = {
      metadata,
      /** Or whatever default value you want. 
      *   Just make sure to create a new instance of it for each call.
      */
      segments: {},
    };
    return segmentMap;
  }, {});
 

Комментарии:

1. Да, это было определенно так. Спасибо за ответ! Просто пошло с: « segments: {…defaultSegmentData }, «