Разбираем как не словить утечку памяти на обработчиках событий!
Одна из частых причин утечек — обработчик повесили, а снять забыли. Особенно это неприятно, когда событие висит не на локальном элементе, а на чём-то долгоживущем:
Компонента уже нет, а
Правильнее сразу закладывать
Главное — снимать нужно ту же самую функцию.
Вот так не сработает:
Это две разные функции, даже если выглядят одинаково.
Нормальный вариант:
Ещё момент —
Так обработчик не снимется, потому что
Когда обработчиков несколько, удобно использовать
А если обработчик нужен только один раз:
После первого вызова он удалится сам. Важный нюанс: удалённый DOM сам по себе ещё не утечка. Если на него нет внешних ссылок — GC его заберёт.
🔥 Проблемы начинаются, когда: обработчик висит на
📣 Code Ready | #практика
Одна из частых причин утечек — обработчик повесили, а снять забыли. Особенно это неприятно, когда событие висит не на локальном элементе, а на чём-то долгоживущем:
window, document, глобальный event bus и т.п.function mount() {
window.addEventListener('resize', onResize);
}
function unmount() {
// забыли снять обработчик
}
Компонента уже нет, а
onResize всё ещё вызывается. Плюс callback может держать в замыкании старое состояние, DOM-ноды, данные — и GC не сможет это нормально прибрать.Правильнее сразу закладывать
cleanup:function mount() {
window.addEventListener('resize', onResize);
}
function unmount() {
window.removeEventListener('resize', onResize);
}
Главное — снимать нужно ту же самую функцию.
Вот так не сработает:
el.addEventListener('click', () => doSomething());
el.removeEventListener('click', () => doSomething());
Это две разные функции, даже если выглядят одинаково.
Нормальный вариант:
function handler() {
doSomething();
}
el.addEventListener('click', handler);
el.removeEventListener('click', handler);
Ещё момент —
capture.el.addEventListener('click', handler, true);
el.removeEventListener('click', handler, false);
Так обработчик не снимется, потому что
capture отличается. Для удаления браузер сравнивает type, listener и capture.passive, once, signal в этом сравнении не участвуют.Когда обработчиков несколько, удобно использовать
AbortController:const controller = new AbortController();
window.addEventListener('resize', onResize, {
signal: controller.signal
});
document.addEventListener('visibilitychange', onVisible, {
signal: controller.signal
});
// cleanup
controller.abort();
abort() снимет все обработчики, которые были добавлены с этим signal.А если обработчик нужен только один раз:
button.addEventListener('click', handler, { once: true });
После первого вызова он удалится сам. Важный нюанс: удалённый DOM сам по себе ещё не утечка. Если на него нет внешних ссылок — GC его заберёт.
window / document; callback держит старое состояние в замыкании; DOM-нода или данные лежат в массиве, store или кэше.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14❤8🤝7