Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як замикання й забуті обробники подій спричиняють витоки пам'яті?

JavaScript звільняє пам'ять автоматично: збирач сміття видаляє об'єкти, до яких неможливо дістатися від «коренів» (глобальних об'єктів, стеку викликів, активних обробників). Витік - це об'єкт, який уже не потрібен, але на який досі хтось посилається.

Замикання тримає змінні своєї області видимості, поки живе сама функція:

function attachHandler(element) {
  const hugeReport = buildReport();       // 50 МБ даних

  element.addEventListener('click', () => {
    console.log(hugeReport.title);        // замикання тримає hugeReport
  });
}

Поки обробник зареєстрований, hugeReport у пам'яті. Якщо обробнику потрібен лише заголовок - краще захопити лише його: const title = hugeReport.title.

Типові джерела витоків у фронтенді:

  • обробники на глобальних об'єктах (window, document), додані компонентом і не зняті при його знищенні:
// при кожному монтуванні компонента - новий обробник, старі живуть вічно
window.addEventListener('resize', this.onResize);
  • таймери: setInterval, що посилається на дані компонента, працює й після того, як компонент зник зі сторінки;
  • від'єднані вузли DOM: елемент видалено зі сторінки, але посилання на нього лишилося в масиві, кеші чи замиканні - разом з ним живе все його піддерево;
  • кеші без обмеження: Map, куди дані лише додають;
  • спостерігачі й підписки: IntersectionObserver, WebSocket-обробники, підписки на сховище стану.

Як прибирати:

const controller = new AbortController();

window.addEventListener('resize', onResize, { signal: controller.signal });
document.addEventListener('keydown', onKey, { signal: controller.signal });

// при знищенні компонента - одним викликом знімаються всі обробники
controller.abort();

У фреймворках для цього є хуки знищення: onUnmounted у Vue, функція очищення в useEffect React, destroy() в Alpine-компонентах.

Для кешів - WeakMap (запис зникає разом з об'єктом-ключем) чи обмеження розміру з витісненням.

Як знайти витік: у DevTools вкладка Memory - зробити знімок купи, повторити дію кілька разів (відкрити й закрити модальне вікно), зробити ще знімок і порівняти. Об'єкти, кількість яких росте з кожним повтором, і поле Detached для від'єднаних вузлів DOM вказують на винуватця. Панель Retainers показує ланцюжок посилань, що тримає об'єкт.

Особливо важливо для SPA і wire:navigate: сторінка не перезавантажується, тож витоки накопичуються годинами роботи.

Докладніше в документації: Керування пам'яттю

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання