Что происходит с функцией в Map, пока на Map есть ссылка в JavaScript
Что проверяют
Отличаете ли вы коллекцию Map от метода массива map, и понимаете ли, что сборщик мусора решает не по полезности объекта, а по достижимости от корней. Вопрос почти всегда ведёт к разговору об утечках памяти.
Как отвечать
Коротко: пока жив сам Map, живы все его ключи и значения. Коллекция держит их сильными ссылками, поэтому функция, положенная туда обработчиком, не будет собрана, даже если больше нигде не упоминается. Вместе с ней в памяти остаётся всё её замыкание: захваченные переменные, узел DOM, ответ сервера.
Отсюда классическая утечка реестра:
const handlers = new Map();
handlers.set(node, () => hide(node));
node.remove(); // узла нет в документе, но он жив: его держит MapЧто предлагать:
- удалять запись явно (
handlers.delete(node)) там же, где снимаете подписку; - либо брать
WeakMap, ключ которого объект не удерживает: пропала последняя обычная ссылка на узел, запись исчезает сама. Плата за это в том, чтоWeakMapне перебирается и не знает своего размера, а ключом может быть только объект. Для наборов естьWeakSet, для точечных случаевWeakRef.
Полезно добавить, что удаление узла из документа само по себе память не освобождает: узел жив, пока на него кто-то ссылается, а запись в коллекции это тоже ссылка.
Где спотыкаются
Отвечают про Array.prototype.map и рассказывают, что метод возвращает новый массив и не меняет исходный. Вопрос про коллекцию, подмена видна сразу, и дальше беседа уходит в проверку базовых знаний вместо обсуждения памяти.
Считают, что WeakMap решает всё. Слабый там только ключ: значение живёт, пока жив ключ, и если значение замкнуто на свой же ключ, пара удерживает сама себя и не собирается.
Путают достижимость с полезностью. Фраза «эта функция больше не вызывается» не означает «она собрана»: любая живая ссылка, будь то запись в Map, слушатель на window или замыкание в незавершённом таймере, удерживает и саму функцию, и всё её окружение.