One place for hosting & domains

      объектами

      Знакомство с объектами map и set в JavaScript


      Автор выбрал фонд Open Internet/Free Speech для получения пожертвования в рамках программы Write for DOnations.

      В JavaScript разработчики часто тратят много времени на решение того, какую структуру данных следует использовать. Это вызвано тем, что выбор правильной структуры данных упрощает последующее управление этими данными, экономя время и упрощая понимание кода. Двумя преобладающими структурами для хранения коллекций данных являются объекты и массивы (тип объекта). Разработчики используют объекты для хранения пары ключ/значение и массивы для хранения индексированных списков. Однако, чтобы предоставить разработчикам больше гибкости, в спецификации ECMAScript 2015 появились два новых типа итерируемых объектов: карты (map), которые являются упорядоченными коллекциями пар ключ/значение, и сеты (set), которые содержат список уникальных значений.

      В этой статье мы поговорим об объектах типов map и set, о том, что их отличает и объединяет с объектами и массивами, о свойствах и методах, доступных для этих типов, а также немного попрактикуемся.

      Карты

      Map — это коллекция пар ключ/значение, которая может использовать любой тип данных в качестве ключа и поддерживает порядок своих записей. Карты содержат как характеристики объектов (коллекция пар ключ/значение), так и массивов (упорядоченная коллекция), но имеют больше сходства с объектами. Это связано с тем, что хотя размер и порядок записей сохраняется, как и в массиве, сами по себе записи являются парами ключ/значение, как объекты.

      Карты можно инициализировать с помощью синтаксиса new Map():

      const map = new Map()
      

      В результате будет создана пустая карта:

      Output

      Map(0) {}

      Добавление значений в карту

      Вы можете добавить значения в карту с помощью метода set(). Первый аргумент будет ключом, а второй — значением.

      Представленный синтаксис добавляет в карту три пары ключ/значение:

      map.set('firstName', 'Luke')
      map.set('lastName', 'Skywalker')
      map.set('occupation', 'Jedi Knight')
      

      Здесь мы начинаем понимать, что карты имеют черты объектов и массивов. Как и в случае массива, у нас есть индексированная коллекция, и мы можем увидеть количество элементов в карте по умолчанию. Карты используют синтаксис => для обозначения пар ключ/значение как key => value:

      Output

      Map(3) 0: {"firstName" => "Luke"} 1: {"lastName" => "Skywalker"} 2: {"occupation" => "Jedi Knight"}

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

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

      [ [ 'key1', 'value1'], ['key2', 'value2'] ]
      

      Используя следующий синтаксис, мы можем воссоздать ту же самую карту:

      const map = new Map([
        ['firstName', 'Luke'],
        ['lastName', 'Skywalker'],
        ['occupation', 'Jedi Knight'],
      ])
      

      Примечание. Данный пример использует завершающие запятые, также именуемые как подвешенные запятые. Это практика форматирования JavaScript, когда конечный элемент в серии при объявлении коллекции данных имеет запятую в конце. Хотя это форматирование можно использовать для получения более чистых версий кода и облегчения работы с кодом, использовать его или нет решает разработчик. Дополнительную информацию о завершающих запятых см. в статье о завершающих запятых в веб-документации MDN.

      Кстати, этот синтаксис выглядит так же, как и результат вызова Object.entries() для объекта. Это дает готовый способ для преобразования объекта в карту, как показано в следующем блоке кода:

      const luke = {
        firstName: 'Luke',
        lastName: 'Skywalker',
        occupation: 'Jedi Knight',
      }
      
      const map = new Map(Object.entries(luke))
      

      Также вы можете превратить карту обратно в объект или массив с помощью одной строки кода.

      Код ниже преобразует карту в объект:

      const obj = Object.fromEntries(map)
      

      В результате будет получено следующее значение obj:

      Output

      {firstName: "Luke", lastName: "Skywalker", occupation: "Jedi Knight"}

      Теперь мы преобразуем карту в массив:

      const arr = Array.from(map)
      

      В результате будет получен следующий массив arr:

      Output

      [ ['firstName', 'Luke'], ['lastName', 'Skywalker'], ['occupation', 'Jedi Knight'] ]

      Ключи карты

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

      Давайте проинициализируем карту нестроковыми ключами:

      const map = new Map()
      
      map.set('1', 'String one')
      map.set(1, 'This will be overwritten')
      map.set(1, 'Number one')
      map.set(true, 'A Boolean')
      

      В этом примере будет переопределен первый ключ 1 на последующий ключ и будет рассмотрено использование строки '1' и числа 1 в качестве уникальных ключей:

      Output

      0: {"1" => "String one"} 1: {1 => "Number one"} 2: {true => "A Boolean"}

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

      В качестве примера выполните инициализацию объекта с числовым ключом и сравните числовой ключ 1 со строковым ключом "1":

      // Initialize an object with a numerical key
      const obj = { 1: 'One' }
      
      // The key is actually a string
      obj[1] === obj['1']  // true
      

      Поэтому, если вы попробуете использовать объект в качестве ключа, он будет отображать строку object Object.

      В качестве примера создайте объект и используйте его в качестве ключа другого объекта:

      // Create an object
      const objAsKey = { foo: 'bar' }
      
      // Use this object as the key of another object
      const obj = {
        [objAsKey]: 'What will happen?'
      }
      

      В результате вы получите следующее:

      Output

      {[object Object]: "What will happen?"}

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

      // Create an object
      const objAsKey = { foo: 'bar' }
      
      const map = new Map()
      
      // Set this object as the key of a Map
      map.set(objAsKey, 'What will happen?')
      

      Ключом элемента карты теперь является созданный нами объект.

      Output

      key: {foo: "bar"} value: "What will happen?"

      Необходимо отметить один важный момент, касающийся использования объекта или массива в качестве ключа: карта использует ссылку на объект для сравнения, а не литеральное значение объекта. В JavaScript {} === {} возвращает false, поскольку оба объекта не являются одинаковыми объектами, несмотря на то, что у них одно (пустое) значение.

      Это означает, что при добавлении двух уникальных объектов с одинаковым значением будет создана карта с двумя записями:

      // Add two unique but similar objects as keys to a Map
      map.set({}, 'One')
      map.set({}, 'Two')
      

      В результате вы получите следующее:

      Output

      Map(2) {{…} => "One", {…} => "Two"}

      Но при использовании одной ссылки на объект дважды будет создана карта с одной записью.

      // Add the same exact object twice as keys to a Map
      const obj = {}
      
      map.set(obj, 'One')
      map.set(obj, 'Two')
      

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

      Output

      Map(1) {{…} => "Two"}

      Второй вызов set() обновляет тот же ключ, что и первый вызов, поэтому в итоге мы получаем карту с одним значением.

      Получение и удаление элементов карты

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

      Мы можем проинициализировать новую карту для демонстрации следующих методов и свойств: delete(), has(), get() и size.

      // Initialize a new Map
      const map = new Map([
        ['animal', 'otter'],
        ['shape', 'triangle'],
        ['city', 'New York'],
        ['country', 'Bulgaria'],
      ])
      

      Используйте метод has() для проверки наличия элемента в карте. has() будет возвращать булево значение.

      // Check if a key exists in a Map
      map.has('shark') // false
      map.has('country') // true
      

      Используйте метод get() для получения значения по ключу.

      // Get an item from a Map
      map.get('animal') // "otter"
      

      Одно конкретное преимущество карт по сравнению с объектами состоит в том, что вы можете получить размер карты в любой момент, как и в случае с массивом. Вы можете получить количество элементов карты с помощью свойства size. Это позволяет использовать меньше действий по сравнению с преобразованием объекта в массив и получением его длины.

      // Get the count of items in a Map
      map.size // 4
      

      Используйте метод delete(), чтобы удалить элемент карты по ключу. Метод будет возвращать булево значение — true, если элемент существовал и был удален, и false, если такой элемент не найден.

      // Delete an item from a Map by key
      map.delete('city') // true
      

      В результате будет получена следующая карта:

      Output

      Map(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}

      Наконец, вы можете удалить все значения карты с помощью map.clear().

      // Empty a Map
      map.clear()
      

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

      Output

      Map(0) {}

      Ключи, значения и записи для карт

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

      Методы keys(), values() и entries() возвращают MapIterator, который похож на массив, где вы можете использовать цикл for...of для прохождения по всем значениям.

      Здесь представлен другой пример карты, который мы можем использовать для демонстрации этих методов:

      const map = new Map([
        [1970, 'bell bottoms'],
        [1980, 'leg warmers'],
        [1990, 'flannel'],
      ])
      

      Метод keys() возвращает ключи:

      map.keys()
      

      Output

      MapIterator {1970, 1980, 1990}

      Метод values() возвращает значения:

      map.values()
      

      Output

      MapIterator {"bell bottoms", "leg warmers", "flannel"}

      Метод entries() возвращает массив пар ключ/значение:

      map.entries()
      

      Output

      MapIterator {1970 => "bell bottoms", 1980 => "leg warmers", 1990 => "flannel"}

      Итерация по карте

      Карта имеет встроенный метод forEach, как и в случае с массивом, для встроенной итерации по карте. Однако есть определенное количество отличий в том, как они выполняют итерацию. Обратный вызов forEach карты проходит по value, key и map, в то время как версия для массива проходит по item, index и array.

      // Map
      Map.prototype.forEach((value, key, map) = () => {})
      
      // Array
      Array.prototype.forEach((item, index, array) = () => {})
      

      Это огромное преимущество карты над объектом, так как объект нужно преобразовывать для использования keys(), values() или entries(), а простого способа получения свойств объекта без преобразования не существует.

      Чтобы продемонстрировать это, давайте пробежимся по нашей карте и выведем пары ключ/значение в консоль:

      // Log the keys and values of the Map with forEach
      map.forEach((value, key) => {
        console.log(`${key}: ${value}`)
      })
      

      Это даст нам следующее:

      Output

      1970: bell bottoms 1980: leg warmers 1990: flannel

      Поскольку цикл for...of поддерживает итерируемые структуры, такие как карты и массивы, мы можем получить аналогичный результат, деструктурируя массив элементов карты:

      // Destructure the key and value out of the Map item
      for (const [key, value] of map) {
        // Log the keys and values of the Map with for...of
        console.log(`${key}: ${value}`)
      }
      

      Свойства и методы карты

      В нижеследующей таблице содержится список свойств и методов карты для быстрого просмотра:

      Свойства/методы Описание Возвращает
      set(key, value) Добавляет пару ключ/значение в карту Карта Объект
      delete(key) Удаляет пару ключ/значение из карты по ключу Булево значение
      get(key) Возвращает значение по ключу значение
      has(key) Проверяет наличие элемента в карте по ключу Булево значение
      clear() Удаляет все элементы из карты Нет данных
      keys() Возвращает все ключи в карте Объект MapIterator
      values() Возвращает все значения в карте Объект MapIterator
      entries() Возвращает все ключи и значения в карте в виде [key, value] Объект MapIterator
      forEach() Выполняет итерацию по карте в порядке добавления Нет данных
      size Возвращает количество элементов в карте Число

      Когда нужно использовать карту

      В целом карты напоминают объекты внутри, которые хранят пары ключ/значение, но карты обладают рядом преимуществ над объектами:

      • Размер — карты имеют свойство size, в то время как объекты не имеют встроенного способа получения размера.
      • Итерация — карты являются итерируемыми, а объекты нет.
      • Гибкость — карты могут содержать любой тип данных (примитивы или объекты) в качестве ключа для значения, а объекты могут хранить только строки.
      • Упорядоченность — карты сохраняют порядок добавления, а объекты не имеют гарантированного порядка.

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

      • JSON — объекты отлично работают с JSON.parse() и JSON.stringify(), двумя основными функциями для работы с JSON, стандартным форматом данных, с которым работает множество REST API.
      • Работа с одним элементом — работая с известным значением в объекте, вы можете получить к нему доступ напрямую с помощью ключа без необходимости использования метода, например метода get() карты.

      Этот список поможет вам решить, подходит ли в вашем конкретном случае карта или объект.

      Сет

      Сет — это коллекция уникальных значений. В отличие от карты, сет концептуально больше походит на массив, а не объект, поскольку он представляет собой список значений, а не пар ключ/значение. Однако сет не является заменой массива, он служит дополнением для предоставления новых возможностей работы с дублирующимися данными.

      Вы можете инициализировать сеты с помощью синтаксиса new Set().

      const set = new Set()
      

      В результате будет создан пустой сет:

      Output

      Set(0) {}

      Элементы можно добавить в сет с помощью метода add() (не путайте с методом set(), доступным для карты, хотя они и похожи).

      // Add items to a Set
      set.add('Beethoven')
      set.add('Mozart')
      set.add('Chopin')
      

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

      set.add('Chopin') // Set will still contain 3 unique values
      

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

      Также вы можете инициализировать сеты с помощью массива значений. Если в массиве есть дублирующие значения, они будут удалены из сета.

      // Initialize a Set from an Array
      const set = new Set(['Beethoven', 'Mozart', 'Chopin', 'Chopin'])
      

      Output

      Set(3) {"Beethoven", "Mozart", "Chopin"}

      Аналогично сет можно преобразовывать в массив с помощью одной строки кода:

      const arr = [...set]
      

      Output

      (3) ["Beethoven", "Mozart", "Chopin"]

      Сеты имеют множество общих с картой методов и свойств, включая delete(), has(), clear() и size.

      // Delete an item
      set.delete('Beethoven') // true
      
      // Check for the existence of an item
      set.has('Beethoven') // false
      
      // Clear a Set
      set.clear()
      
      // Check the size of a Set
      set.size // 0
      

      Обратите внимание, что сет не позволяет получить доступ к значению по ключу или индексу, как, например, Map.get(key) или arr[index].

      Ключи, значения и записи для сетов

      Карта и сет имеют методы keys(), values() и entries(), которые возвращают итератор. Однако, хотя каждый из этих методов имеет конкретную функцию в карте, у сетов нет ключей, поэтому ключи являются псевдонимами для значений. Это означает, что keys() и values() будут возвращать один и тот же итератор, а entries() будет возвращать значение дважды. В случае с сетом только метод values()​​​ имеет смысл, а два других метода существуют для обеспечения согласованности и кросс-совместимости с картой.

      const set = new Set([1, 2, 3])
      // Get the values of a set
      set.values()
      

      Output

      SetIterator {1, 2, 3}

      Итерация сета

      Как и карта, сет имеет встроенный метод forEach(). Поскольку у сетов нет ключей, первый и второй параметр обратного вызова forEach() возвращает одно и то же значение, так что нет необходимости его использования без совместимости с картой. Параметрами forEach() являются (value, key, set).

      При работе с сетом можно использовать forEach() и for...of. Вначале рассмотрим итерацию с помощью forEach():

      const set = new Set(['hi', 'hello', 'good day'])
      
      // Iterate a Set with forEach
      set.forEach((value) => console.log(value))
      

      Затем мы сможем написать версию for...of:

      // Iterate a Set with for...of
      for (const value of set) {  
          console.log(value);
      }
      

      В обоих случаях нам потребуется следующее:

      Output

      hi hello good day

      Свойства и методы сета

      В нижеследующей таблице содержится список свойств и методов сета для быстрого просмотра:

      Свойства/методы Описание Возвращает
      add(value) Добавляет новый элемент в сет Сет Объект
      delete(value) Удаляет заданный элемент из сета Булево значение
      has() Проверяет наличие элемента в сете Булево значение
      clear() Удаляет все элементы из сета Нет данных
      keys() Возвращает все значения сета (аналогично values()) Объект SetIterator
      values() Возвращает все значения сета (аналогично keys()) Объект SetIterator
      entries() Возвращает все значения сета в виде [value, value] Объект SetIterator
      forEach() Выполняет итерацию по сету в порядке вставки Нет данных
      size Возвращает количество элементов сета Число

      Когда нужно использовать сет

      Сет — это полезное дополнение для вашего набора инструментов JavaScript, особенно при работе с дублирующими значениями в данных.

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

      const uniqueArray = [ ...new Set([1, 1, 2, 2, 2, 3])] // (3) [1, 2, 3]
      

      Это даст нам следующее:

      Output

      (3) [1, 2, 3]

      Сет можно использовать для поиска идентичных, пересекающихся и отличных элементов двух наборов данных. Однако массивы имеют ряд преимуществ, включая дополнительные инструменты манипуляции данными благодаря методам sort(), map(), filter() и reduce(), а также прямую совместимость с методами JSON.

      Заключение

      Из этой статьи мы узнали, что карта представляет собой коллекцию упорядоченных пар ключ/значение, а сет — это коллекция уникальных значений. Обе эти структуры дают JavaScript дополнительные возможности и упрощают выполнение стандартных задач, например поиск длины коллекции пар ключ/значение и удаление дублирующих элементов из набора данных соответственно. С другой стороны, объекты и массивы традиционно используются для хранения и обработки данных в JavaScript, а также имеют прямую совместимость с JSON, что позволяет им оставаться самыми важными структурами данных, особенно при работе с REST API. Карты и сеты часто используются в качестве вспомогательных структур данных для объектов и массивов.

      Если вы хотите узнать больше о JavaScript, перейдите на главную страницу нашей серии материалов Написание кода на JavaScript или перейдите к нашей серии Написание кода на Node.js для изучения материалов по серверной разработке.



      Source link