Коллекции JavaScript – Set, Map, WeakMap и WeakSet

Spread the love

Введение

В этой статье мы познакомимся с новыми коллекциями в JavaScript:

  • Set
  • Map
  • WeakMap
  • WeakSet

Все они были введены в спецификацию JavaScript с ES2015, также известным как ES6.

И все они являются итерируемыми структурами. Так же, как String и Array. Это означает, что мы можем использовать for… of для итерации по этим коллекциям и получения доступа к каждому из их элементов.

Set

Set это набор уникальных значений, которые могут быть любого типа. Это буквально означает, что вы можете поместить любое значение в Set, и да, вы можете смешивать типы внутри набора, как в обычных массивах JavaScript. Эта коллекция похожа на HashSet в мире C#, Set/HashSet в Java и Set в Python.

Синтаксис:

new Set([iterable]);

iterable — какой нибудь итерируемый объект. Проще говоря «итерируемый» — это то, что мы можем перебирать, и обычно это последовательность или набор элементов.

Давайте посмотрим на пример с Set:

let set = new Set([3, 5, true, 'This is a string, obviously.']);

for (let item of set.values()) {
  console.log(item);
}
/* Вывод:
3
5
true
This is a string, obviously.
*/

Поскольку сам Set является итеративным, мы можем использовать оператор for..of :

let set = new Set([3, 5, true, 'This is a string, obviously.']);

for (let item of set) {
  console.log(item);
}
/* Вывод:
3
5
true
This is a string, obviously.
*/

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

let set = new Set([3, 5, true, 'This is a string, obviously.']);

for (let item of set.values()) {
  console.log(item);
}

console.log('Adding a new value');
set.add(5);

for (let item of set.values()) {
  console.log(item);
}

Set имеет очень полезное свойство под названием size, которое дает нам информацию о количестве значений внутри коллекции.

Наиболее важные методы, присутствующие в экземпляре Set:

  • add(element) – Добавляет новый элемент в Set
  • clear() – Удаляет все элементы из Set
  • delete(element) – Удаляет указанный элемент из Set
  • forEach – Это как классический цикл forEach для массива
  • has(value) – Возвращает true, если коллекция содержит данный элемент.
  • toString() – Распечатывает “[object Set]”
  • values() – Возвращает все значения коллекции Set
  • keys() – То же, что и values(). Это псевдоним для метода values(). Единственная причина, по которой он существует, — иметь единый интерфейс для новых типов коллекций в JavaScript.

forEach для Set

Синтаксис:

mySetInstance.forEach(function callback(value1, value2, Set) { 
 // some code that will be run for every value inside ofSet
}[, thisArg])

Если вы знакомы с синтаксисом forEach для Array, вы заметите, что он имеет такую же подпись. За исключением того, что с массивом у нас есть индекс в качестве второго аргумента.

Для записи Map также использует тот же синтаксис, однако второй аргумент является ключем.

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

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

Третий аргумент представляет объект Set.

Получить массив из Set

Мы можем просто преобразовать Set в Array, используя деструктуризацию:

let set = new Set([9, 15]);

set.add(44);

let arr = [...set];

console.log(arr); // [9, 15, 44]

Получить уникальные значения из массива с помощью Set

Как мы уже говорили, Set — это коллекция, которая имеет уникальные значения, то есть не может иметь дубликатов. Мы только что увидели, как мы можем получить Array из Set, рассмотрим как мы можем с помощью Set получить массив уникальных элементов из Array (или из любого итеративного объекта).

Пример:

let arr = [ 13, 11, 15, 21, 13, 11, 17, 17, 19, 19, 21 ];

let uniqueArray = [...new Set(arr)];

console.log(uniqueArray); // [13, 11, 15, 21, 17, 19]

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

Map

Map в основном представляет собой набор пар ключ/значение. Это похоже на Dictionary в C#, Map в Java, Hash в Ruby и Dictionary в Python.

Map также имеет свойство под названием size, которое дает нам информацию о количестве ключей/значений внутри коллекции.

Следующие методы присутствуют в экземпляре Map:

  • clear() – Удаляет все элементы с Map
  • delete(key) – Удаляет указанный элемент из Map
  • forEach – как классический цикл forEach для массива
  • get(key) – Получает элемент для указанного ключа
  • has(key) – Возвращает true, если коллекция содержит элемент с этим ключом
  • keys() – Возвращает все ключи коллекции Map
  • set(key, value) – Добавляет новый элемент на карту
  • values() – Возвращает все значения коллекции Map
  • toString() – Распечатывает “[object Set]”

Вот пример создания нового объекта Map:

let map = new Map([['name', 'CodingBlast'], ['points', 33], [true, 55], ['true', 44]])

for (let [key, value] of map.entries()) {
  console.log('key is ' + key + ', value is ' + value);
}
/* Вывод:
key is name, value is CodingBlast
key is points, value is 33
key is true, value is 55
key is true, value is 44
*/

Мы также можем просмотреть список ключей или значений:

for (let key of map.keys()) {
  console.log('key:' + key);
}

for (let value of map.values()) {
  console.log('value:' + value);
}

Вопрос есть ли способ просто распечатать все ключи или значения данного Map. Да, есть!

Мы можем использовать удобный метод Array.from:

let map = new Map([['name', 'CodingBlast'], ['points', 33], [true, 55], ['true', 44]])

console.log(Array.from(map.keys()))
console.log(Array.from(map.values()))
console.log(Array.from(map.entries()))
/*
(4) ["name", "points", true, "true"]
(4) ["CodingBlast", 33, 55, 44]
(4) [Array(2), Array(2), Array(2), Array(2)]
*/

Последняя строка console.log просто распечатает тот же массив, который мы указали при создании экземпляра карты. Ну, это массив, который имеет массивы в качестве элементов.

Map против Object

Вы, наверное, задаетесь вопросом, зачем нам использовать Map, если мы можем просто использовать объекты JavaScript. Они очень похожи на Map, и так же состоят из пар ключ/значения. Основные отличия Map от Object:

  • В Map у нас есть несколько удобных встроенных методов, а также свойство size, позволяющее легко определить, сколько значений мы храним внутри Map.
  • В Map все может быть ключом, в то время как с обычными объектами мы должны использовать только строки в качестве ключей.
  • В Map доступ/изменение значения элемента осуществляется с помощью Map.prototype.get(key) / Map.prototype.set(key, value)

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

let obj = {};

console.log(obj['toString'])

console.log(obj['hasOwnProperty'])

console.log(obj['constructor'])
/*
ƒ toString() { [native code] }
ƒ hasOwnProperty() { [native code] }
ƒ Object() { [native code] }
*/

Но, конечно же, мы можем использовать встроенный метод hasOwnProperty, чтобы проверить, действительно ли ключ находится в объекте или в одном из прототипов. Также мы обычно используем метод Object.keys(), чтобы получить только собственные и перечисляемые ключи данного объекта.

Список критериев выбора Map или Object:

1. Object является отличным выбором для сценариев, когда нам нужна простая структура для хранения данных и мы знаем, что все ключи являются либо строками, либо целыми числами (или символами), потому что создание простого объекта и доступ к свойству объекта с определенным ключом намного быстрее, чем создание с Map.

2. Кроме того, в сценариях, где существует необходимость применять отдельную логику к отдельным свойствам/элементам, Object, безусловно, является выбором. Например:

var obj = {
    id: 1, 
    name: "It's Me!", 
    print: function(){ 
        return `Object Id: ${this.id}, with Name: ${this.name}`;
    }
}
console.log(obj.print());//Object Id: 1, with Name: It's Me.

3. Более того, JSON имеет прямую поддержку Object, но не с Map (пока). Поэтому в определенной ситуации, когда нам приходится много работать с JSON, рассмотрите Object как предпочтительный вариант.

4. В противоположность, Map является чисто хеш-функцией, Object — чем-то большим (с поддержкой внутренней логики). А использование оператора delete со свойством Object имеет несколько проблем с производительностью. Следовательно, в сценариях, которые требуют много добавления и удаления (особенно) новой пары, Map может работать намного лучше.

5. Кроме того, Map сохраняет порядок своих ключей — в отличие от Object, и Map был создан с учетом итерации, поэтому в случае, если итерация или порядок элементов очень важны, рассмотрите Map — он обеспечит стабильную производительность итерации во всех браузерах.

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

Cлабые коллекции, Память и Сборщик мусора

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

Ссылки на объекты внутри Map и Set всегда сохраняются и не позволяют собирать мусор. Это может дорого обойтись, если Map/Set ссылаются на большие объекты, которые больше не нужны, такие как элементы DOM, которые уже были удалены из DOM.

Чтобы исправить это, ES6 также представляет две новые слабые коллекции под названием WeakMap и WeakSet. Эти коллекции ES6 являются «слабыми», поскольку они допускают удаление объектов из памяти, которые больше не нужны.

WeakMap

WeakMap — третья из новых коллекций ES6, которые мы представляем. WeakMap похожи на обычные Map, хотя с меньшим количеством методов и вышеупомянутой разницей в отношении сбора мусора.

const aboutAuthor = new WeakMap(); // Create New WeakMap
const currentAge = {}; // key must be an object
const currentCity = {}; // keys must be an object

aboutAuthor.set(currentAge, 30); // Set Key Values
aboutAuthor.set(currentCity, 'Denver'); // Key Values can be of different data types

console.log(aboutAuthor.has(currentCity)); // Test if WeakMap has a key

aboutAuthor.delete(currentAge); // Delete a key

Случаи применения

У WeakMap есть несколько популярных вариантов использования. Их можно использовать для обеспечения конфиденциальности личных данных объекта, а также для отслеживания узлов / объектов DOM.

Пример использования c личными данными

var Person = (function() {
  var privateData = new WeakMap();

  function Person(name) {
    privateData.set(this, { name: name });
  }

  Person.prototype.getName = function() {
    return privateData.get(this).name;
  };

  return Person;
}());

Использование WeakMap упрощает процесс сохранения конфиденциальности данных объекта. Можно ссылаться на объект Person, но доступ к privateData WeakMap запрещен без конкретного экземпляра Person.

Случай использования с DOM-узлов

Проект Google Polymer использует WeakMap во фрагменте кода под названием PositionWalker.

PositionWalker отслеживает положение в поддереве DOM в качестве текущего узла и смещение в этом узле.

WeakMap используется для отслеживания изменений, удалений и изменений узла DOM:

_makeClone() {
  this._containerClone = this.container.cloneNode(true);
  this._cloneToNodes = new WeakMap();
  this._nodesToClones = new WeakMap();

  ...

  let n = this.container;
  let c = this._containerClone;

  // find the currentNode's clone
  while (n !== null) {
    if (n === this.currentNode) {
    this._currentNodeClone = c;
    }
    this._cloneToNodes.set(c, n);
    this._nodesToClones.set(n, c);

    n = iterator.nextNode();
    c = cloneIterator.nextNode();
  }
}

WeakSet

WeakSets — это наборы коллекций, элементы которых можно собирать, когда объекты, на которые они ссылаются, больше не нужны. WeakSet не допускают итерации. Их варианты использования довольно ограничены (на данный момент, по крайней мере). Большинство ранних последователей говорят, что WeakSet можно использовать для пометки объектов без их изменения. ES6-Features.org содержит пример добавления и удаления элементов из WeakSet для отслеживания того, были ли объекты помечены или нет:

let isMarked     = new WeakSet()
let attachedData = new WeakMap()

export class Node {
    constructor (id)   { this.id = id                  }
    mark        ()     { isMarked.add(this)            }
    unmark      ()     { isMarked.delete(this)         }
    marked      ()     { return isMarked.has(this)     }
    set data    (data) { attachedData.set(this, data)  }
    get data    ()     { return attachedData.get(this) }
}

let foo = new Node("foo")

JSON.stringify(foo) === '{"id":"foo"}'
foo.mark()
foo.data = "bar"
foo.data === "bar"
JSON.stringify(foo) === '{"id":"foo"}'

isMarked.has(foo)     === true
attachedData.has(foo) === true
foo = null  /* remove only reference to foo */
attachedData.has(foo) === false
isMarked.has(foo)     === false

Заключение

В этой статье мы рассмотрели новые коллекции в JavaScript: Set, Map, WeakMap, WeakSet. Разницу между Set и Map. Что выбрать между Map и Object. А так же для чего можно использовать WeakMap и WeakSet

Была ли вам полезна эта статья?
[26 / 4.7]

Spread the love
Подписаться
Уведомление о
guest
2 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Alexander
Alexander
3 лет назад

Отличный материал для подготовки к собеседованию. И спасибо за публикацию ссылки на мой блог (в другой статье) 😉

Альбина
Альбина
3 лет назад

Спасибо за материал, мне помогла инфа, долго искала что-то подобного формата