Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як уникнути витоків пам'яті?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Немає посилання - немає витоку.** Все, що залишається досяжним з коренів (глобальні змінні, замикання, DOM, кеші), не збереться сміттєзбірником. **Ключове:** потрібно скорочувати час життя об'єктів і вчасно розривати зв'язки - обробники, таймери, кеші, посилання на DOM.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## 1) Базові принципи - **Немає посилання - немає витоку.** Все, що залишається досяжним з коренів (глобальні змінні, замикання, DOM, кеші), не збереться сміттєзбірником. - **Скорочуйте час життя об'єктів** і вчасно розривайте зв'язки (обробники, таймери, кеші, посилання на DOM). ## 2) Типові джерела витоків і як їх закрити ### Обробники подій / підписки **Проблема:** висячі слухачі тримають DOM/дані. ```javascript const onClick = () => {}; button.addEventListener('click', onClick); // ... button.removeEventListener('click', onClick); // обов'язково тим самим колбеком ``` - У React: завжди робіть **cleanup у** `useEffect`. ```javascript useEffect(() => { const onScroll = () => {}; window.addEventListener('scroll', onScroll); return () => window.removeEventListener('scroll', onScroll); // }, []); ``` - У Nest/EventEmitter: знімаємо підписки/відписуємось у `onModuleDestroy()`. ### Таймери та інтервали ```javascript const id = setInterval(tick, 1000); // ... clearInterval(id); // ``` - Node 20+: використовуйте **AbortSignal** для таймерів: ```javascript const ac = new AbortController(); setTimeout(cb, 5000, { signal: ac.signal }); // ... ac.abort(); // ``` ### Незавершені fetch/стріми - Скасовуйте через `AbortController`, закривайте стріми. ```javascript const c = new AbortController(); const res = await fetch(url, { signal: c.signal }); // ... c.abort(); // ``` ### "Відірвані" DOM-вузли - Не зберігайте посилання на видалені з DOM ноди. - Обнуляйте кеші з DOM після `remove()`: ```javascript elem.remove(); elemRef = null; // даємо GC шанс ``` ### Великі замикання - Не замикайте **великі об'єкти** без потреби. Передавайте **мінімум** у колбеки. - Розривайте посилання після використання: ```javascript let big = getHugeObject(); doWork(() => { /* використовуємо small part */ }); big = null; // ``` ### Глобальні синглтони/кеші - Все, що в модулі/глобалі, живе "вічно". Обмежуйте розмір, додавайте очищення або слабкі посилання. ## 3) Кеші без витоків - Ключі-об'єкти → **WeakMap/WeakSet** (не заважають GC): ```javascript const memo = new WeakMap(); function compute(obj) { if (memo.has(obj)) return memo.get(obj); const val = heavy(obj); memo.set(obj, val); return val; } ``` - Для рядків/чисел використовуйте **LRU** з обмеженням: ```javascript class LRU { constructor(limit=500){ this.limit=limit; this.map=new Map(); } get(k){ const v=this.map.get(k); if(v){ this.map.delete(k); this.map.set(k,v); } return v; } set(k,v){ if(this.map.has(k)) this.map.delete(k); this.map.set(k,v); if(this.map.size>this.limit){ const oldest = this.map.keys().next().value; this.map.delete(oldest); // витісняємо } } } ``` ## 4) React / Next.js - **Cleanup** усіх побічних ефектів (`useEffect` → `return () => {...}`). - Не зберігайте величезні об'єкти в `useRef`/контексті без потреби. - Уникайте нескінченних мікрозадач (ланцюжки `Promise`), вони затримують GC і рендер. - Відписуйтесь від **Observable/WebSocket** у cleanup. - Для великих списків - віртуалізація (менше об'єктів одночасно). ## 5) Node.js / NestJS - Закривайте з'єднання/пули: - Prisma: `await prisma.$disconnect()` при завершенні. - Redis/Mongo/pg: `.quit()`/`.close()`/`.end()` у `onModuleDestroy()`. - Стріми: завжди `.destroy()`/`.end()` при помилках і таймаутах. - EventEmitter: `emitter.removeListener`/`removeAllListeners` при вивантаженні. - Уникайте безконтрольних `setInterval`; використовуйте `setTimeout` **+ цикл** з перевірками/abort. - Обробляйте сигнали: ```javascript process.on('SIGTERM', async () => { await app.close(); // закрийте сервер/БД/черги process.exit(0); }); ``` ## 6) Асинхронність і черги - Встановлюйте **таймаути** і **ліміти на паралелізм** (p-limit, семафори). - На мережах/стрімах - **backpressure**: не читайте швидше, ніж обробляєте. - Не тримайте великі масиви промісів у пам'яті; обробляйте чанками. ## 7) FinalizationRegistry - лише для м'якого прибирання - Підходить для зовнішніх кешів/хендлів, але **не покладайтесь на порядок/час** виклику. - Бізнес-логіку на фіналізаторах не будуємо. ## 8) Інструменти пошуку витоків ### Браузер (Chrome) - **Memory → Heap snapshot**: шукайте "Detached HTML elements", ретейнери. - **Allocation instrumentation**: хто створює і утримує. - **Performance**: довгі мікрозадачі/часті GC - ознака проблеми. ### Node - `--inspect` + Chrome DevTools → Heap snapshots. - `heapdump`/`clinic` для знімків у бойовому середовищі. - Логи GC: `node --trace-gc app.js` (для діагностики, не в prod). - Метрики: моніторте RSS/heapUsed, алерти на тренд зростання. ## 9) Гардрейли в коді і CI - Ліміти на розміри кешів/черг. - Е2Е-тест "прогріли N разів → пам'ять стабілізувалась". - Правила лінтера: заборона анонімних слухачів там, де потрібне зняття, заборона `setInterval` без явного clear.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.