Массивы - это часть нашей повседневной жизни как программистов. Представим, что вы создаете приложение для электронной торговли и в какой-то момент у вас есть массив, подобный следующему:

const skus = [
  {
    "productId": 1,
    "colorId": 1,
    "sku": "11-M"
  },
  {
    "productId": 1,
    "colorId": 1,
    "sku": "11-L"
  },
  {
    "productId": 1,
    "colorId": 2,
    "sku": "12-M"
  },
  {
    "productId": 1,
    "colorId": 2,
    "sku": "12-L"
  },
  {
    "productId": 2,
    "colorId": 1,
    "sku": "21-M"
  },
  {
    "productId": 2,
    "colorId": 1,
    "sku": "21-L"
  }
];

Учитывая этот массив, вашей новой задачей будет получение всех SKU для определенного productId. Первое, что придет вам в голову, это использование skus.find(s => productId === s.productId), и это, вероятно, будет хорошим решением для некоторых сценариев. Однако, когда ваше приложение обрабатывает большой объем данных, это может привести к проблемам с производительностью.

Лучшим подходом для этого случая является преобразование этого массива в индекс для прямого доступа к SKU вместо итерации по массиву.

Для этого мы можем использовать reduce:

const productIndex = indexByField(skus, “productId”);

и наш индекс будет:

{
  "1": [
    {
      "productId": 1,
      "colorId": 1,
      "sku": "11-M"
    },
    {
      "productId": 1,
      "colorId": 1,
      "sku": "11-L"
    },
    {
      "productId": 1,
      "colorId": 2,
      "sku": "12-M"
    },
    {
      "productId": 1,
      "colorId": 2,
      "sku": "12-L"
    }
  ],
  "2": [
    {
      "productId": 2,
      "colorId": 1,
      "sku": "21-M"
    },
    {
      "productId": 2,
      "colorId": 1,
      "sku": "21-L"
    }
  ]
}

Теперь вы можете получить все артикулы для конкретного продукта!

Подождите ... как я могу получить артикулы для определенного цвета? 🤔

Как вы могли заметить, мы проиндексировали наш массив только по productId. Давайте также проиндексируем наш массив по colorId.

Принимая во внимание текущий статус индекса, лучше всего проиндексировать его значения с помощью нашей функции indexByField. Да, в данном случае это сработает, но что, если бы у нас было много уровней индексации? …

Рекурсия приходит на помощь!

Давайте объясним это mapObjectValues подробно:

  • value будет нашим массивом при первом вызове и индексом при следующих рекурсивных вызовах.
  • shouldMap определит, когда прекратить выполнение рекурсивных вызовов. В нашем случае это будет Array.isArray.
  • mapFn преобразует значения наших индексов, как Array.prototype.map для элементов массива. В нашем случае это будет наша indexByField функция.

Собираем все вместе 🧩

Мы готовы определить нашу функцию для индексации массива по нескольким полям. Кроме того, мы расширяем Array.prototype, чтобы мы могли вызывать его прямо из нашего экземпляра:

const productColorIndex = skus.indexBy(“productId”, "colorId");

и его значение будет:

{
  "1": {
    "1": [
      {
        "productId": 1,
        "colorId": 1,
        "sku": "11-M"
      },
      {
        "productId": 1,
        "colorId": 1,
        "sku": "11-L"
      }
    ],
    "2": [
      {
        "productId": 1,
        "colorId": 2,
        "sku": "12-M"
      },
      {
        "productId": 1,
        "colorId": 2,
        "sku": "12-L"
      }
    ]
  },
  "2": {
    "1": [
      {
        "productId": 2,
        "colorId": 1,
        "sku": "21-M"
      },
      {
        "productId": 2,
        "colorId": 1,
        "sku": "21-L"
      }
    ]
  }
}

Теперь мы можем получить артикулы, просто просмотрев наш индекс:

productColorIndex["1"]["2"];

Подводя итог 📝

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

Однако этот выигрыш обходится дорого, создание индекса и его хранение в памяти в некоторых сценариях может быть очень дорогостоящим.

Ресурсы