С ES6 Карта и Набор были введены в JavaScript. Наконец, они привносят в ваши веб-приложения более сложные структуры данных. Из-за этого они являются очень желанным дополнением к языку. Слишком долго массивы были вынуждены решать все существующие проблемы (однако я не вижу, чтобы эта проблема изменилась в ближайшее время). Но, на мой взгляд, они сильно промазали по реализации.

Краткое знакомство с картой и набором

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

  • клавиши не ограничиваются строками, цифрами и символами
  • map - это коллекция, то есть она имеет размер, порядок и может повторяться.
  • карты предназначены для работы с дополнительными ключами, а не с обязательными ключами. Это более важно при использовании TypeScript и обеспечении безопасности типов.

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

Где все пошло не так

Основываясь на приведенных выше пунктах, можно было бы позабавить, что карта и набор являются прекрасным дополнением к языку. И были бы, если бы не то, насколько спартанским является их интерфейс. Давайте начнем с Map. Для этого аргумента я представлю пример использования карты:

Допустим, я хочу увеличить счетчик для определенной клавиши, и когда ключ не задан заранее, он должен стать 1. С картой ES6, которая будет смотреть где-то вокруг строк этого:

Это большой объем кода, если вы понимаете, что вся цель карты - иметь дело с парами ключ-значение! Например, в immutable.js это будет записано с помощью одного вызова update:

Примечание. Мне нравится immutable.js, но я не говорю, что вы должны использовать его вместо «обычных» карт и наборов. Неизменяемые и изменяемые структуры данных - это разные концепции, и они не просто взаимозаменяемы.

Другой пример большого скупца - отсутствие методов преобразования в Map и Set. Нет map для преобразования каждого значения в коллекции и нет filter для исключения значений из карты или набора. Допустим, мы хотим удалить все нулевые счетчики. С картой ES6 нам понадобится цикл for с новой картой для этого:

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

Последний пример, который я хочу привести по поводу Map, - это отсутствие функции merge. Распространенная задача с картами - объединить две карты вместе и определенным образом разрешить конфликтующие ключи (обычно переопределение последней или заданного настраиваемого слияния). С картой ES6 это снова потребует цикла for и некоторого оператора if. Можно было бы ожидать, что просто сделаете это с map.merge(otherMap).

Все вышеперечисленные пункты также применимы к Set, поскольку это очень сопоставимая структура данных с картой. Главным дополнительным скрягой здесь является отсутствие merge, except и both. Set обычно хороши при написании кода, который заботится о перекрытии различий между несколькими наборами значений, но не в JavaScript.

Наконец: помните, что Map и Set - это упорядоченные коллекции? Их нельзя отсортировать, кроме как преобразовать их в массив, отсортировать массив и воссоздать вашу карту или набор. Почему бы вам явно реализовать упорядоченную карту, но не разрешать пользователю упорядочивать свою карту?

Как это сделать правильно

После всех жалоб пришло время поговорить о том, как это сделать правильно. Для начала: будьте последовательны в своих интерфейсах при разработке стандартной библиотеки. Если я могу отображать, фильтровать и сокращать один тип коллекции определенным образом, я ожидаю, что все остальные будут вести себя аналогичным образом. Set, Map и Array должны иметь сопоставимые интерфейсы. В идеале это должен быть интерфейс ICollection, который обеспечивает согласованность всей коллекции (как для встроенных, так и для пользовательских реализаций).

Другой момент - подумать о том, почему существуют эти коллекции. Если цель души коллекции состоит в том, чтобы, скажем, работать с парами ключ-значение, я бы ожидал, что будет все, что нужно для создания, удаления и изменения значений с их ключами. Когда Set в первую очередь предназначен для работы с коллекциями уникальных значений, я ожидаю, что будет все, чтобы действительно использовать эту возможность, а не реализовывать стандартные библиотечные функции, такие как merge и except. Особенно на языке сценариев высокого уровня, таком как JavaScript, который разработан, чтобы быть простым в использовании.

В конце статьи я включил фрагмент кода с реализацией большинства упомянутых функций. На самом деле, большинство из них действительно просты. Это просто боль, когда вы должны думать об этих основных функциях, в то время как ваш мозг должен быть сосредоточен на проблеме, которую вы пытаетесь решить в первую очередь.

Заключение

Что вы думаете о Map и Set в JavaScript? Вы их часто используете или вы похожи на меня и все еще используете сторонние библиотеки для структур данных? На мой взгляд, новые структуры данных ES6 недостаточно хороши, чтобы быть практичными. Я подпишу это одним из примеров реализации большинства упомянутых мною функций. Просто чтобы показать, что сделать все правильно с первого раза не должно было особого труда.