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. Это тоже может быть интересно:

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.