Skip to main content

Таймери та слухачі подій

1. Що роблять таймери та слухачі подій

  • setTimeout, setInterval, addEventListener створюють асинхронні задачі, які живуть окремо від основного стека виконання JS.
  • Ці задачі утримують посилання на все, що використовується всередині колбеків (через замикання).

Поки таймер активний або слухач "підписаний", усі об'єкти, до яких звертається його callback, вважаються досяжними → GC не може їх видалити.

2. Чому важливо очищати

Без очищення - об'єкти залишаються в пам'яті назавжди

javascript
function startTimer() { const bigData = new Array(1e6).fill('data'); setInterval(() => console.log(bigData.length), 1000); } startTimer();

Тут bigData ніколи не звільняється:

  • інтервал живе вічно;
  • замикання утримує посилання на bigData;
  • GC вважає bigData "живим" → пам'ять росте.

Потрібно очищати:

javascript
const id = setInterval(...); clearInterval(id);

Слухачі подій - та сама історія

javascript
const button = document.getElementById('btn'); button.addEventListener('click', () => console.log('clicked')); button.remove(); // елемент видалено, слухач залишився

Тепер:

  • елемент button видалено з DOM, але callback все ще зберігається в пам'яті (у JS залишилось посилання на нього);
  • GC не може видалити об'єкт, тому що на нього "дивиться" зареєстрований обробник.

Рішення:

javascript
button.removeEventListener('click', handler);

3. Чому це особливо критично в SPA-застосунках

У SPA сторінка не перезавантажується. Все, що не очищається, живе, поки користувач не закриє вкладку.

Наприклад:

  • ти переходиш між сторінками;
  • компоненти монтуються й демонтуються;
  • якщо таймери й слухачі не прибираються, кожен "старий" компонент продовжує існувати в пам'яті → накопичується сміття.

Через 30 хвилин роботи React-застосунку можна побачити:

  • сотні активних інтервалів,
  • десятки "зомбі"-слухачів,
  • heap memory росте без повернення.

4. Як саме це утримує пам'ять (механізм)

Об'єктПосиланняЧому не очищається
Таймерколбек (closure)утримує посилання на всі змінні замикання
Слухачcallback + DOM-елементживуть, поки не викликати removeEventListener
WebSocket, RxJS, setIntervalактивна підпискаутримує весь контекст
React useEffectбез cleanupефекти не скасовуються, посилання зберігаються

GC вважає все це досяжним, тому що у нього є активні callback-посилання.

5. Як правильно очищати

Таймери

javascript
useEffect(() => { const id = setInterval(() => {...}, 1000); return () => clearInterval(id); }, []);

Слухачі подій

javascript
useEffect(() => { const handler = () => console.log('scroll'); window.addEventListener('scroll', handler); return () => window.removeEventListener('scroll', handler); }, []);

Підписки (RxJS, WebSocket, custom events)

javascript
useEffect(() => { const sub = stream$.subscribe(...); return () => sub.unsubscribe(); }, []);

6. Що відбувається, якщо не чистити

НаслідокЩо відбувається
Ріст пам'ятіcallback-замикання утримують великі об'єкти
Лаги UIevent loop забитий активними callback-ами
Дублюючі викликистарі слухачі спрацьовують разом з новими
Ріст CPUзабагато активних інтервалів
"Out of memory"браузер або вкладка падає
"Зомбі-компоненти"старі React-компоненти продовжують жити й виконувати ефекти

7. Інструменти для виявлення

  • Chrome DevTools → Memory → Heap snapshot → шукаємо "Detached DOM nodes" та об'єкти Timeout, EventListener.
  • Performance → Record → "Long tasks".
  • React DevTools Profiler → перевіряємо, що компоненти демонтуються.
  • Sentry Performance → ріст heap, витоки, деградація FPS.

8. Резюме

Що потрібно пам'ятатиЧому
setTimeout / setInterval потрібно очищатиінакше callback залишається живим
addEventListener потрібно зніматиінакше DOM і функція не звільняються
У React - завжди return () => cleanup() у useEffectзапобігає "зомбі"-ефектам
У SPA немає "перезапуску" пам'ятівсе накопичується, поки не очистиш вручну
Витоки пам'яті = лаги + падінняGC не може видалити "живі" посилання

Головна думка:

Таймер або слухач - це як відкрита вкладка в пам'яті. Якщо не закрити - таких вкладок стає сотні, і браузер "вмирає".

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.