Как сделать так, чтобы функция в Map могла быть удалена сборщиком мусора в JavaScript
Что проверяют
Понимаете ли вы, что Map держит сильные ссылки и на ключи, и на значения, и знаете ли слабые структуры языка. Следом спрашивают, чем WeakMap отличается от WeakRef и почему нельзя «вызвать сборщик мусора».
Как отвечать
Сборщик удаляет объект, когда до него нельзя дойти от корней: глобального объекта, стека, живых замыканий. Пока функция лежит в Map, она достижима, и память не освободится.
- удалить явно:
map.delete(key)илиmap.clear(). Функция станет кандидатом на сборку, если на неё больше никто не ссылается, включая обработчики событий, таймеры и чужие замыкания; - функция как ключ
WeakMap: функции это объекты, поэтому годятся в ключи.WeakMapне мешает сборке ключа, а значение живёт, пока жив ключ. Подходит, когда к функции надо привязать метаданные или кэш; - функция как значение: если ключ строка,
WeakMapне подойдёт, строки ключом быть не могут. Тогда вMapкладутnew WeakRef(fn)и достают черезref.deref(), который вернётundefined, если функцию уже собрали; FinalizationRegistry: позволяет после сборки удалить изMapосиротевшую запись сWeakRef, чтобы не копились пустые ключи.
Отдельно скажите, что функция тянет за собой всё своё замыкание. Если она захватила большой массив или DOM-узел, то держит в памяти и его. Утечку проверяют снимками кучи на вкладке Memory в DevTools: сравнивают два снимка и в разделе Retainers смотрят, кто держит объект.
Где спотыкаются
Считают, что после delete память освобождается сразу. Сборка недетерминирована, момент выбирает движок, и принудительно вызвать её из кода страницы нельзя.
Кладут функцию значением в WeakMap по объектному ключу и ждут, что сборка пойдёт по значению. Слабым там является только ключ.
Строят логику на FinalizationRegistry, например закрывают соединение в колбэке. Спецификация не гарантирует, что колбэк вызовется вообще, так что он годится только для уборки, а не для обязательных действий. Ещё забывают, что WeakMap нельзя перебрать и у неё нет size.