Привет, Хабр!

Когда дело доходит до коллекций данных в JavaScript, большинство разработчиков сразу вспоминают про массивы, объекты, Map или Set. Но есть и другие, менее известные структуры данных, которые можно назвать «инструментами для особых случаев» — это WeakMap и WeakSet.

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

Однако их область применения не ограничивается только управлением памятью. WeakMap и WeakSet позволяют:

  1. Хранить данные, которые исчезают сами, без явного удаления.

  2. Создавать скрытые хранилища для приватных данных, не изменяя сам объект.

  3. Автоматическое удаление временных данных, связанных с объектами, например, кэшей.

WeakMap

WeakMap — это коллекция пар ключ‑значение, где:

  • Ключами могут быть только объекты.

  • Значениями могут быть любые данные, от строк до функций.

  • Ключи имеют слабую ссылку, что позволяет сборщику мусора удалять их, если они больше нигде не используются.

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

С WeakMap работают всего четыре метода:

  1. set(key, value) — добавляет пару ключ‑значение.

  2. get(key) — возвращает значение, связанное с ключом.

  3. has(key) — проверяет наличие ключа.

  4. delete(key) — удаляет пару ключ‑значение.

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

const weakMap = new WeakMap();

let user = { name: "Alice" };
weakMap.set(user, "Приватные данные пользователя");

console.log(weakMap.get(user)); // "Приватные данные пользователя"

user = null;

// После этого объект автоматически удаляется из WeakMap сборщиком мусора.

Примеры применения

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

const privateData = new WeakMap();

class User {
  constructor(name) {
    this.name = name;
    privateData.set(this, { permissions: [] });
  }

  getPermissions() {
    return privateData.get(this).permissions;
  }

  addPermission(permission) {
    privateData.get(this).permissions.push(permission);
  }
}

const user = new User("Bob");
user.addPermission("admin");
console.log(user.getPermissions()); // ["admin"]

// Если объект user будет удалён, его данные из privateData также исчезнут.

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

const cache = new WeakMap();

function processData(obj) {
  if (cache.has(obj)) {
    return cache.get(obj); // Используем закэшированные данные
  }

  // Сложные вычисления
  const result = { processed: true };
  cache.set(obj, result);

  return result;
}

let data = { id: 1 };

console.log(processData(data)); // { processed: true }
console.log(processData(data)); // { processed: true }

data = null; // Кэш автоматически очищается.

WeakSet

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

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

Методов у WeakSet ещё меньше, чем у WeakMap:

  1. add(value) — добавляет объект.

  2. has(value) — проверяет наличие объекта.

  3. delete(value) — удаляет объект.

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

const weakSet = new WeakSet();

let obj = { id: 1 };

weakSet.add(obj);

console.log(weakSet.has(obj)); // true

obj = null;

// Объект автоматически удаляется из WeakSet.

Примеры применения

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

const processed = new WeakSet();

function process(obj) {
  if (processed.has(obj)) {
    console.log("Объект уже обработан!");
    return;
  }

  processed.add(obj);
  console.log("Обрабатываю объект:", obj);
}

let task = { id: 1 };

process(task); // "Обрабатываю объект"
process(task); // "Объект уже обработан!"

task = null; // WeakSet автоматически очищается.

Так же, работая с DOM, можно использовать WeakSet для отслеживания элементов, которым уже назначены обработчики событий:

const elementsWithHandlers = new WeakSet();

function addClickHandler(element) {
  if (!elementsWithHandlers.has(element)) {
    elementsWithHandlers.add(element);
    element.addEventListener("click", () => console.log("Клик!"));
  }
}

let div = document.createElement("div");

addClickHandler(div); // Привязываем обработчик
addClickHandler(div); // Ничего не делаем, обработчик уже добавлен

div = null; // Обработчики удаляются автоматически.

Хотя использование этих инструментов может показаться ограниченным, они становятся незаменимыми, когда вы работаете с кэшами, приватными данными или сложными DOM‑структурами.

Если есть опыт применения этих инструментов, пишите в комментариях!

Все актуальные инструменты и лучшие практики веб‑разработки можно изучить на онлайн‑курсах в Otus. В каталоге можно посмотреть список всех программ, а в календаре мепроприятий — записаться на бесплатные открытые уроки.

Комментарии (0)