Каковы причины утечек памяти в JavaScript
Что проверяют
Понимаете ли вы, как работает сборщик мусора: память держат достижимые ссылки, а не «забытые переменные». Следом почти всегда просят показать, как вы найдёте утечку в DevTools.
Как отвечать
Начните с механизма. V8 и другие движки используют трассирующий сборщик с поколениями: объект живёт, пока до него можно дойти от корней (глобальный объект, стек, активные замыкания). Утечка это ссылка, которую забыли разорвать. Типичные источники:
- таймеры и подписки:
setInterval, WebSocket, подписка на стор, не снятые при уходе со страницы. Колбэк держит своё замыкание со всеми данными; - обработчики на долгоживущих объектах:
window.addEventListener('resize', ...)в компоненте безremoveEventListener. Удобно снимать пачкой черезAbortControllerи опциюsignal; - отсоединённые DOM-узлы: элемент убрали из документа, но ссылка осталась в массиве или кэше, и всё поддерево живёт в памяти;
- растущие кэши:
Mapбез вытеснения. Если ключ это объект, подходитWeakMap, запись исчезнет вместе с ключом; - общий контекст замыканий: замыкания, созданные в одной функции, делят контекст, и большой объект, нужный одному колбэку, удерживают все;
- React:
useEffectбез функции очистки, запросы без отмены, библиотеки графиков и карт без вызоваdestroy().
Диагностика в Chrome DevTools: вкладка Memory, снимок кучи, затем несколько раз повторить подозрительное действие (открыть и закрыть модалку), запустить сборку кнопкой и снять второй снимок. Режим Comparison покажет, какие объекты копятся, фильтр Detached найдёт отсоединённые узлы, а цепочка Retainers подскажет, кто держит объект.
Где спотыкаются
Называют причиной циклические ссылки. Трассирующему сборщику цикл без внешних ссылок не мешает, это была беда подсчёта ссылок в старых IE.
Считают утечкой любой рост памяти. Сборщик работает лениво, пилообразный график нормален, тревожен рост после принудительной сборки.
Советуют WeakMap как универсальное средство. Ключи у него только объекты, перебрать его нельзя, а утечку из-за таймера или подписки он не лечит.