<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[WeakMap против утечек в event-листенерах: под капотом DOM и кэш]]></title><description><![CDATA[<p dir="auto">Event-листенеры на DOM-нодах часто оставляют утечки памяти. Объект удаляется из DOM, а слушатель держит на него жесткую ссылку через замыкание. WeakMap решает это - слабые ссылки позволяют GC собрать мусор автоматически.</p>
<p dir="auto">Это критично для SPA с динамическим DOM. Без правильного управления кэш пользовательских данных разрастается, и приложение тормозит. Разберем, как WeakMap чистит за собой, на примерах реального кода.</p>
<h2>Классическая утечка: жесткие ссылки в слушателях</h2>
<p dir="auto">Когда добавляешь addEventListener, колбэк захватывает DOM-ноду в замыкании. Нода удаляется из дерева, но слушатель в нейтральной зоне держит ссылку. GC не может ничего собрать - объект висит в памяти зря.</p>
<p dir="auto">В типичном сценарии SPA рендеришь список пользователей. Каждый элемент с клик-хендлером, который тянет за собой данные профиля. Прокручиваешь список, элементы уходят из вида, но память не освобождается. Через час сессии JS-куча раздувается вдвое.</p>
<p dir="auto">Проблема усугубляется кэшем. Хранишь userData по ноде в Map - ключ тоже жесткий. Даже если нода ушла, Map не отпустит. Результат: <strong>утечка растет линейно с действиями юзера</strong>.</p>
<ul>
<li><strong>addEventListener без remove</strong>: Хендлер держит this и замыкания. Удаляй вручную в cleanup, но забьешь - привет утечка.</li>
<li><strong>Замыкания с данными</strong>: function handler() { use(bigUserObject); } - bigUserObject не уйдет.</li>
<li><strong>Глобальный кэш</strong>: const cache = new Map(); cache.set(node, data); - node жив до конца приложения.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Подход</th>
<th>Ссылки на DOM</th>
<th>GC работает?</th>
<th>Риск утечки</th>
</tr>
</thead>
<tbody>
<tr>
<td>Map + жесткие ключи</td>
<td>Жесткие</td>
<td>Нет</td>
<td>Высокий</td>
</tr>
<tr>
<td>WeakMap</td>
<td>Слабые</td>
<td>Да</td>
<td>Низкий</td>
</tr>
<tr>
<td>Ручной removeEventListener</td>
<td>Жесткие до cleanup</td>
<td>Зависит</td>
<td>Средний</td>
</tr>
</tbody>
</table>
<h2>WeakMap под капотом: слабые ключи для нод</h2>
<p dir="auto">WeakMap - это Map, где ключи только объекты, и ссылки на них слабые. GC видит, что на ключ нет других ссылок извне - собирает и ключ, и значение. Идеально для кэша по DOM-нодам.</p>
<p dir="auto">Под капотом WeakMap не хранит счетчик ссылок явно. Браузерный V8 или SpiderMonkey интегрируют его с марк-энд-свип. Когда нода удалена из DOM и нет других ссылок, WeakMap автоматически ее сбрасывает. Нет нужды в ручной чистке.</p>
<p dir="auto">Пример с event-листенером. Вместо хранения хендлера в глобале, кэшируй данные по ноде в WeakMap. Хендлер захватывает WeakMap - но это не жесткая ссылка на ноду.</p>
<pre><code class="language-javascript">const listeners = new WeakMap();

function attachHandler(node, userData) {
  listeners.set(node, userData);
  node.addEventListener('click', function() {
    const data = listeners.get(node);
    console.log(data);
  });
}
</code></pre>
<ul>
<li><em>Нода удалена</em>: GC видит, что на node нет ссылок кроме WeakMap - чистит node и значение.</li>
<li><strong>Автоматично</strong>: Нет setTimeout для cleanup, нет проверки isMounted.</li>
<li>Масштабируемо: тысячи нод - память чистится сама.</li>
</ul>
<h2>Кэш пользовательских данных без костылей</h2>
<p dir="auto">Обычный паттерн: кэш профилей по ноде аватара. Map(node =&gt; userProfile). Node уходит - профиль висит. WeakMap меняет правила: кэш живет ровно столько, сколько нода.</p>
<p dir="auto">В реальном проекте SPA с виртуальным скроллом. Рендеришь 1000+ строк таблицы. Каждая с фото, данными юзера. Без WeakMap кэш сожрет гигабайт за сессию. С WeakMap - память стабильна.</p>
<p dir="auto">Интеграция с событиями. Хендлер клика по аватару тянет tooltip с данными. Данные из WeakMap - если нода жива, данные на месте. Ушла нода - tooltip не сломается, просто undefined.</p>
<pre><code class="language-javascript">const userCache = new WeakMap();

document.addEventListener('click', (e) =&gt; {
  if (e.target.dataset.userId) {
    const node = e.target.closest('.avatar');
    const profile = userCache.get(node);
    if (profile) showTooltip(profile);
  }
});
</code></pre>
<ul>
<li><strong>Производительность</strong>: O(1) доступ, как в Map.</li>
<li><em>Нюанс</em>: Значения тоже могут быть объектами - GC их тоже проверит.</li>
<li>Плюс WeakRef: Для значений, если нужно отложенное чтение.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Map</th>
<th>WeakMap</th>
<th>Пример использования</th>
</tr>
</thead>
<tbody>
<tr>
<td>Жесткие ключи</td>
<td>Слабые ключи</td>
<td>Кэш по DOM-элементам</td>
</tr>
<tr>
<td>Нужно чистить вручную</td>
<td>Авто-GC</td>
<td>Event-данные</td>
</tr>
<tr>
<td>Глобальные ключи</td>
<td>Только объекты</td>
<td>Пользовательские профили</td>
</tr>
</tbody>
</table>
<h2>Реальные грабли и как их обойти</h2>
<p dir="auto">Новички лепят WeakMap куда попало, забывая, что ключи - только объекты. Строка в ключ - Error. Или думают, что WeakMap вечный - нет, он чистится при GC.</p>
<p dir="auto">Частая ошибка: хендлер захватывает переменную извне замыкания. listeners.get(node) внутри, но if (externalVar) {} - externalVar держит все. Делай чистые функции.</p>
<p dir="auto">В React-подобных фреймворках lifecycle-хуки. useEffect добавляет слушатель - в cleanup removeEventListener. Но с WeakMap можно не чистить: GC сам разберется. Только если хендлер не захвачен родителем.</p>
<ul>
<li><strong>Грабли 1</strong>: <code>const strongRef = node;</code> перед WeakMap - убивает слабость.</li>
<li><em>Грабли 2</em>: Массив нод в глобале - все утечки вернулись.</li>
<li><strong>Тест</strong>: Chrome DevTools &gt; Memory &gt; Heap snapshot. С WeakMap delta = 0.</li>
</ul>
<h2>Когда WeakMap не панацея</h2>
<p dir="auto">WeakMap спасает от 80% утечек в DOM-кэше. Но если данные нужны после удаления ноды - комбинируй с IndexedDB или sessionStorage. Или WeakRef для ленивой загрузки.</p>
<p dir="auto">Осталось за кадром: полифиллы для старых браузеров, WeakMap в Node.js воркерах. Подумай, как интегрировать с ResizeObserver - там тоже ноды летают. Тестируй в продакшене с реальными данными.</p>
]]></description><link>https://forum.exlends.com/topic/2169/weakmap-protiv-utechek-v-event-listenerah-pod-kapotom-dom-i-kesh</link><generator>RSS for Node</generator><lastBuildDate>Thu, 23 Apr 2026 14:20:41 GMT</lastBuildDate><atom:link href="https://forum.exlends.com/topic/2169.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 23 Apr 2026 07:13:09 GMT</pubDate><ttl>60</ttl></channel></rss>