Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Таймери та слухачі подій». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)`setTimeout`, `setInterval` та `addEventListener` створюють асинхронні задачі, які **утримують посилання** на все, що використовується всередині колбеків (через замикання). **Ключове:** поки таймер активний або слухач "підписаний", усі об'єкти, до яких звертається його callback, вважаються досяжними, тому GC не може їх видалити.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## 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-замикання утримують великі об'єкти | | Лаги UI | event 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 не може видалити "живі" посилання | **Головна думка:** > Таймер або слухач - це як відкрита вкладка в пам'яті. > Якщо не закрити - таких вкладок стає сотні, і браузер "вмирає".Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.