ES6: объекты WeakMap, Set и WeakSet изнутри
Перевод статьи ES6 WeakMaps, Sets, and WeakSets in Depth с сайта ponyfoo.com, опубликовано на css-live.ru с разрешения автора — Николаса Беваквы.
Встречайте очередную часть «ES6 — это уже невыносимо — изнутри». Вы здесь впервые? Тогда начните с краткой историей инструментария ES6. Затем изучите деструктирование, литералы шаблона, стрелочные функции, оператор расширения и оставшиеся параметры, улучшения в литералах объекта, новый «сахарок» — классы поверх прототипов, let
, const
и «Временную мёртвую зону», а такжеитераторы игенераторы и символы и объекты Map. Ну а этим утром мы обсудим ещё три структуры данных для коллекций, новинки в ES6: объекты WeakMap
, Set
, и WeakSet
.
Как и в прошлых статьях, рекомендую вам установить Babel и повторять за мной, копируя примеры с помощью интерактивной оболочки REPL, либо командной строки babel-node и файла. Это поможет гораздо лучше усвоить идеи, обсуждаемые в серии. Если вы не из тех, кто любит устанавливать что-либо на свой компьютер, то вам есть смысл залезть на CodePen и кликнуть иконку с шестерёнкой для JavaScript — у него есть препроцессор Babel, который с лёгкостью позволяет опробовать ES6. Ещё одна довольно полезная альтернатива, это использовать онлайновый REPL для Babel — он показывает скомпилированный ES5-код справа от ES6-кода, чтобы быстро сравнить.
Пока мы не начали, позвольте беззастенчиво попросить вашей поддержки, если вы наслаждаетесь моей серией «ES6 изнутри». Ваши взносы пойдут на хостинг сайту, мне на еду, на то, чтоб я не отставал от графика, и на то, чтобы Pony Foo оставался поистине источником джаваскриптовых вкусняшек.
Спасибо, что выслушали, а теперь перейдём к коллекциям! Чтобы напомнить себе, о чем вообще речь, можете заглянуть в статью про итераторы — которые тесно связаны с коллекциями в ES6 — и в статью про оператор расширения и оставшиеся параметры.
А теперь вспомним, на чём мы остановились — пора познакомиться с объектом
WeakMap
.
Объекты WeakMap в ES6
Можете рассматривать объект WeakMap
как подмножество Map
. У WeakMap
есть несколько ограничений, отсутствующих в Map. Самое главное ограничение WeakMap
в том, что в отличие от Map
он не итерируемый — а значит нет протокола «Итерируемый», методов .entries()
, .keys()
, values()
, .forEach()
и .clear()
.
Другое «ограничение», присутствующее в WeakMap
и которого нет в Map
— каждый ключ должен быть объектом, а типы значения не принимаются в качестве ключей. Заметьте, что Symbol
— также тип значения, и его это тоже касается.
var map = new WeakMap() map.set(1, 2) // TypeError: 1 — это не объект! map.set(Symbol(), 2) // TypeError: недопустимое значение используется в качестве ключа объекта WeakMap
Впрочем, это не столько проблема, сколько фича, поскольку сборщик мусора может подчищать ключи ассоциативного массива, если ключи в
WeakMap
— единственная ссылка на эти объекты. Обычно это бывает нужно при хранении метаданных для чего-нибудь вроде DOM-ноды, и теперь такие метаданные можно хранить вWeakMap
. Если же вам нужно всё перечисленное выше, всегда можно воспользоваться обычным объектомMap
, как мы уже рассматривали.
Можете по-прежнему передать итерируемый объект для заполнения объекта WeakMap
через его конструктор.
var map = new WeakMap([[new Date(), 'foo'], [() => 'bar', 'baz']])
Как и в случае с Map
, можно использовать .has
, .get
и также .delete
.
var date = new Date() var map = new WeakMap([[date, 'foo'], [() => 'bar', 'baz']]) console.log(map.has(date)) // <- true console.log(map.get(date)) // <- 'foo' map.delete(date) console.log(map.has(date)) // <- false
Это определённо хуже Map?
Знаю! Вам, видимо, интересно — какого черта я использую WeakMap
, когда у него столько ограничений по сравнению с Map
?
Ощутимая разница в WeakMap
отражается в его названии. WeakMap
хранит слабые ссылки на свои ключи, что означает, что если других ссылок на какой-то один из его ключей нет, этот объект подлежит сборке мусора.
Применения WeakMap
в основном сводятся к необходимости указывать метаданные и расширять объект, сохранив возможность удалить его при сборке мусора, если никакому другому коду нет дела до этого объекта. Прекрасным примером служит реализация, лежащая в основе process.on('unhandledRejection')
, которая использует WeakMap
, чтобы отслеживать те промисы, которые были отклонены, но это никак не было обработано в рамках текущего вызова process.nextTick
Хранение данных о DOM-элементах, которые следует удалить из памяти, если они больше не нужны — ещё одно очень важное применение, и поэтому WeakMap
, возможно, будет даже лучшим решением для кеширования API для работы с DOM, вроде того, что мы писали чуть раньше с помощью Map.
Так что однозначно нет. WeakMap
точно не хуже Map
— они просто «заточены» под разные задачи.
Объекты Set в ES6
Объекты Set — ещё один тип коллекций в ES6. Объекты Set очень похожи на Map
. А именно:
- Объект
Set
также итерируемый - Конструктору объекта
Set
можно передавать итерируемые объекты - У объекта
Set
также есть свойство.size
- У ключей также могут быть произвольные значения
- Ключи должны быть уникальны
NaN
равенNaN
, когда это касается объектаSet
- Объект
Set
поддерживает методы.keys
,.values
,.entries
,.forEach
,,.get
,.set
.has
,.delete
и.clear
Однако, есть также и несколько отличий!
- У объектов
Set
могут быть только значения - Нет
set.get
— но где бы вам понадобилосьget(value) => value
? - Было бы странно, если бы было
set.set
, так что вместо него у нас естьset.add
set[Symbol.iterator] !== set.entries
set[Symbol.iterator] === set.values
set.keys === set.values
set.entries()
возвращает итератор для последовательности элементов, вроде[value, value]
В примере ниже можно заметить, как Set
принимает итерируемый объект с повторяющимися значениями, как он может заполнить Array
с помощью оперетора расширения, и как игнорируются повторяющиеся значения.
var set = new Set([1, 2, 3, 4, 4]) console.log([...set]) // <- [1, 2, 3, 4]
Объекты Set
могут стать прекрасной альтернативой для работы с DOM-элементами. В следующем коде создаётся объект Set со всеми элементами <div>
на странице, а после выводится количество найденных <div>
. Затем мы снова обращаемся к DOM и вызываем set.add
для каждого DOM-элемента. Поскольку элементы уже в наборе, свойство .size
не изменится, а следовательно объект Set
останется таким же.
function divs () { return [...document.querySelectorAll('div')] } var set = new Set(divs()) console.log(set.size) // <- 56 divs().forEach(div => set.add(div)) console.log(set.size) // <- 56 // <- взгляни-ка, они не повторяются!
Объекты WeakSet
Как и в случае с WeakMap
и Map
, WeakSet
— это Set плюс слабые ссылки минус итерируемость (я только что придумал термин, не так ли?)
Это значит, что нельзя перебирать WeakSet
, его значения должны быть ссылками на уникальный объект. Если на найденное в WeakSet
value
ничего не ссылается, объект подлежит сборке мусора.
Как и в WeakMap
, в WeakSet
можно только добавлять значения с помощью .add
, проверять с помощью .has
и удалять c помощью .delete
. И так же, как и в Set
, у WeakSet
нет метода .get
.
var set = new WeakSet() set.add({}) set.add(new Date())
Как нам известно, нельзя использовать примитивные значения.
var set = new WeakSet() set.add(Symbol()) // TypeError: недопустимое значение, которое используется в слабом наборе
Как и с WeakMap
, передача итераторов в конструктор по-прежнему разрешена, даже несмотря на то, что сам экземпляр WeakSet
не итерируемый.
var set = new WeakSet([new Date(), {}, () => {}, [1]])
У WeakSet
могут найтись разные применения, вот пример из обсуждения в почтовой рассылке по спецификации JavaScript (ECMAScript-262).
const foos = new WeakSet() class Foo { constructor() { foos.add(this) } method () { if (!foos.has(this)) { throw new TypeError('Foo.prototype.method вызван для несовместимого объекта!') } } }
Хорошим правилом будет также проверять, не подойдет ли WeakSet
, когда вы подумываете об использовании WeakMap
, поскольку в некоторых задачах подходит и то, и другое. В частности, если всё, что нужно проверить — есть ли значение ссылочного типа в WeakSet
.
На следующей неделе полакомимся на завтрак вкусняшкой прокси (
Proxy
) :)
P.S. Это тоже может быть интересно: