WeakSet и WeakMap в JavaScript

WeakSet и WeakMap - это слабые коллекции. Они похожи на Set и Map, но работают только с объектами и не мешают сборщику мусора удалить объект из памяти.

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

Что значит слабая коллекция

В JavaScript память очищается автоматически. Если объект больше нигде не используется, движок может удалить его из памяти.

Обычные Map и Set хранят сильную ссылку на объект. Пока объект лежит в такой коллекции, он считается используемым.

let users = new Set()
let user = { name: 'Alex' }

users.add(user)

user = null

В примере объект все еще остается в Set, поэтому он не пропал окончательно.

WeakSet и WeakMap хранят ссылку слабее. Если объект больше нигде не нужен, наличие в слабой коллекции не будет удерживать его в памяти.

WeakMap

WeakMap хранит данные в формате ключ - значение, как обычный Map. Главное отличие - ключами могут быть только объекты.

let visits = new WeakMap()

let user = { name: 'Alex' }

visits.set(user, 5)

visits.get(user) // 5

Значением может быть любое значение: число, строка, массив, объект или функция.

Методы WeakMap

У WeakMap есть только несколько основных методов.

set()

Добавляет значение по объекту-ключу.

let userData = new WeakMap()
let user = { name: 'Alex' }

userData.set(user, { role: 'admin' })

get()

Возвращает значение по ключу.

userData.get(user) // { role: 'admin' }

has()

Проверяет, есть ли ключ в коллекции.

userData.has(user) // true

delete()

Удаляет данные по ключу.

userData.delete(user) // true

Пример использования WeakMap

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

let userStatus = new WeakMap()

let user = {
  name: 'Alex',
}

userStatus.set(user, 'online')

userStatus.get(user) // 'online'

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

WeakSet

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

let checkedUsers = new WeakSet()

let user = { name: 'Alex' }

checkedUsers.add(user)

checkedUsers.has(user) // true

Методы WeakSet

У WeakSet есть три основных метода.

add()

Добавляет объект в коллекцию.

let processedUsers = new WeakSet()
let user = { name: 'Alex' }

processedUsers.add(user)

has()

Проверяет, есть ли объект в коллекции.

processedUsers.has(user) // true

delete()

Удаляет объект из коллекции.

processedUsers.delete(user) // true

Пример использования WeakSet

WeakSet можно использовать для отметки уже обработанных объектов.

let processedUsers = new WeakSet()

function processUser(user) {
  if (processedUsers.has(user)) {
    return
  }

  processedUsers.add(user)

  console.log(`Обрабатываем ${user.name}`)
}

let user = { name: 'Alex' }

processUser(user) // Обрабатываем Alex
processUser(user) // повторной обработки не будет

Такой подход не меняет сам объект user и не добавляет в него технические свойства.

Ограничения WeakSet и WeakMap

Слабые коллекции нельзя перебрать. У них нет forEach(), keys(), values(), entries() и свойства size.

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

let userData = new WeakMap()

userData.size // undefined

Также в WeakMap и WeakSet нельзя добавить примитивы.

let weakSet = new WeakSet()

weakSet.add('Alex') // TypeError

Когда использовать

Если нужно хранить список, считать количество элементов или перебирать коллекцию, лучше использовать обычные Set и Map.

WeakSet и WeakMap подходят для служебных данных, которые привязаны к объектам и не должны удерживать эти объекты в памяти.

Итого

WeakMap хранит дополнительные данные по объектам-ключам. WeakSet хранит только объекты и помогает отмечать их состояние. В отличие от обычных коллекций, они не перебираются и не показывают размер, зато не мешают удалению объектов из памяти.