Производительность приложений играет ключевую роль в удержании пользователей. Даже небольшое замедление может привести к потере клиентов и снижению конверсии. Одной из частых причин деградации производительности являются утечки памяти – ситуации, при которой программа не освобождает память, которая ей больше не нужна. Давайте разберемся, почему это происходит и как с этим бороться.
Что такое утечка памяти в JavaScript
Когда вы пишете код на JavaScript, компьютер выделяет память для хранения переменных, объектов и других данных. Но если эта память не освобождается вовремя (и правильно), возникает явление, называемое утечкой памяти.
Пример из жизни: представьте, что у вас есть ящик для хранения вещей. Если вы постоянно кладете в него новые вещи, не убирая старые и ненужные, рано или поздно ящик переполнится. Утечка памяти – это тот же самый переполненный «ящик», только в вашей программе.
Что вызывает утечку памяти в JavaScript
В JavaScript утечка памяти происходит, когда память, которая больше не используется, не освобождается, и программа продолжает ее удерживать. Это приводит к тому, что:
- Приложение начинает потреблять все больше памяти.
- Работа приложения ощутимо замедляется.
- В крайних случаях приложение может зависнуть или вылететь из-за нехватки памяти.
Как JavaScript управляет памятью с помощью сборщика мусора

В JavaScript управление памятью происходит с помощью механизма под названием сборщик мусора. По аналогии с уже упомянутым ящиком для хранения это уборщик, который регулярно проверяет ваш ящик (память) и выкидывает все ненужные вещи (результаты работы программы).
Как работает сборщик мусора:
- Находит неиспользуемые переменные, объекты и функции. Если у объекта больше нет ссылок (он нигде не используется), JavaScript определяет его как «мусор».
- Освобождает занятую мусором память, автоматически удаляя неиспользуемые данные.
Почему сборщик мусора допускает утечки
Иногда уборщик не справляется со своей задачей. Например:
- Объекты остаются связаны друг с другом (через замыкания или глобальные переменные), даже если они больше не нужны.
- Уборщик видит, что есть связи между объектами, и ошибочно считает их нужными.
В результате такой мусор остается в памяти, и возникает утечка.
Основные причины утечек памяти в JavaScript и как их избежать
Глобальные переменные
Почему возникает проблема: глобальные переменные остаются в памяти в течение всего времени работы приложения. Если случайно создать переменную в глобальной области видимости, она будет занимать память, даже если уже не нужна. Здесь переменная leakyVar создается в глобальной области из-за отсутствия ключевого слова let или const:
// Пример утечки памяти
function createGlobalVariable() {
leakyVar = "Я глобальная переменная!"; // переменная объявлена без 'let' или 'const'
createGlobalVariable();
Решение: всегда объявляйте переменные с помощью let или const, чтобы ограничить их область видимости.
function createGlobalVariable() {
let safeVar = "Я локальная переменная!"; // Локальная область видимости
}
createGlobalVariable();
Незакрытые слушатели событий
Почему возникает проблема: eсли добавить слушатель события (event listener), он будет удерживать ссылку на объект. Если элемент удалить из DOM, но не убрать слушатель, память останется занятой:
// Пример утечки памяти
const button = document.getElementById("myButton");
button.addEventListener("click", () => {
console.log("Кнопка нажата!");
});
// Если удалить кнопку из DOM, но оставить слушатель,
// память для button останется занятой.
document.body.removeChild(button);
Решение: перед удалением элемента из DOM необходимо удалить его слушатели событий.
// Сохраняем ссылку на функцию
function handleClick() {
console.log("Кнопка нажата!");
}
button.addEventListener("click", handleClick);
// Перед удалением кнопки — снимаем слушатель
button.removeEventListener("click", handleClick);
document.body.removeChild(button);
Ссылки на удаленные DOM-элементы
Почему возникает проблема: если в коде остается ссылка на DOM-элемент, который уже удален со страницы, память, занимаемая этим элементом, не будет освобождена. Здесь память не освободится, так как переменная cachedDiv все еще содержит ссылку на удаленный элемент:
// Пример утечки памяти
let cachedDiv = document.getElementById("myDiv");
// Удаляем элемент из DOM
document.body.removeChild(cachedDiv);
// Но переменная cachedDiv все еще хранит ссылку на удаленный элемент
Решение: после удаления DOM-элемента из памяти нужно обнулить ссылки на него.
// Освобождаем память, обнуляя ссылку
cachedDiv = null;
Замыкания
Замыкание возникает, когда внутренняя функция запоминает переменные из внешней области видимости, даже если внешняя функция уже завершила работу. Замыкания — мощный инструмент, но их неправильное использование может привести к утечкам памяти.
Почему возникает проблема: если замыкание хранит ссылку на большие объекты или массивы, то эти данные останутся в памяти, даже если уже не нужны. В приведенном ниже примере:
- Переменная largeArray создается внутри функции createClosure().
- Вложенная функция запоминает largeArray через замыкание.
- Когда функция createClosure() завершает работу, большой массив остается в памяти, потому что на него ссылается переменная leakyClosure.
function createClosure() {
const largeArray = new Array(1000000); // Большой массив
return function() {
console.log(largeArray.length);
};
}
const leakyClosure = createClosure();
Решение:
- Избегайте ненужных замыканий. Если большой объект не нужен в замыкании, не оставляйте его внутри.
- Не создавайте замыкания внутри циклов без необходимости.
- Разрывайте ссылки на большие объекты, если они больше не нужны.
function createSafeClosure() {
let largeArray = new Array(1000000);
// Очищаем память после использования
largeArray = null;
return function() {
console.log("Замыкание создано без утечки памяти!");
};
}
const safeClosure = createSafeClosure();
Как обнаружить утечки памяти
Современные браузеры оснащены инструментами для разработчиков, которые помогают выявлять проблемы с памятью. Например:
- В Chrome откройте «Инструменты разработчика» (нажмите F12) и перейдите во вкладку «Память».
- Запишите снимок памяти и найдите объекты, которые остаются в памяти дольше, чем должны.
- Сравните несколько снимков памяти, сделанных с интервалом. Если одни и те же объекты остаются между снимками, это признак утечки.
- Отслеживайте потребление памяти вашим приложением в реальном времени. Если график использования памяти постоянно растет и не снижается после выполнения задач, это указывает на возможную утечку памяти.
В заключение
Чтобы предотвратить утечку памяти, нужно:
- Избегать глобальных переменных. Применяйте let и const, так как они ограничивают область видимости переменных.
- Очищать слушатели событий. При работе с DOM обязательно удаляйте слушатели событий, когда они больше не нужны.
- Разрывать ссылки на большие объекты. Устанавливайте ненужные переменные и объекты в null, чтобы освободить память.
- Аккуратно использовать замыкания. Они могут удерживать ссылки на переменные, что приводит к утечкам, особенно в циклах, таймерах и интервалах.
- Регулярно мониторить использование памяти. Используйте браузерные инструменты разработчика или специализированные инструменты.
При подготовке статьи использовалась публикация «Memory Leaks in JavaScript: A Simple Guide».
Весенний апгрейд навыков: -35% на курсы по программированию от Proglib Academy
- Основы IT для непрограммистов – для рекрутеров, маркетологов, проджект- и продакт-менеджеров
- Frontend Basic – стек технологий для старта в веб-разработке (HTML, CSS, React, Git, JavaScript)
- Математика для Data Science – подготовка к решению задач уровня FAANG-компаний
- Алгоритмы и структуры данных – глубокое погружение для подготовки к техническим собеседованиям
- Базовые модели ML – введение в машинное обучение с фокусом на tree-based модели
- Архитектуры и шаблоны проектирования – освоите основные паттерны проектирования и прокачаете свои навыки архитектора программного обеспечения
Комментарии