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