Утечки памяти

Flatpickr создаёт инстансы календаря, которые связываются с DOM-элементами, событиями и внутренними структурами состояния. В типичных сценариях браузер самостоятельно очищает память при удалении DOM-узлов, однако при наличии активных ссылок, подписок на события и замыканий сборщик мусора не может освободить объекты. Это приводит к постепенному росту потребления памяти, особенно в SPA-приложениях, где компоненты часто монтируются и размонтируются.

Основной источник проблем связан не с самой библиотекой, а с неправильным жизненным циклом экземпляра календаря.


Жизненный цикл экземпляра Flatpickr

Каждый вызов flatpickr() создаёт объект инстанса, содержащий:

  • ссылки на исходный input-элемент
  • сгенерированную DOM-структуру календаря
  • обработчики событий (click, input, keydown)
  • внутренние кеши дат и конфигурации
  • ссылки на глобальные функции форматирования

При этом инстанс регистрирует обработчики на document и window, что делает его живым даже при удалении исходного элемента из DOM.


Типовые причины утечек памяти

Отсутствие вызова destroy()

Наиболее частая проблема — уничтожение DOM без освобождения инстанса.

const fp = flatpickr("#date", {});

document.querySelector("#date").remove();

В этом случае элемент исчезает из DOM, но:

  • обработчики событий остаются активными
  • календарь может быть открыт в памяти
  • ссылки на конфигурацию сохраняются

Корректное завершение жизненного цикла требует явного вызова:

fp.destroy();

destroy() удаляет DOM календаря, снимает все события и разрывает внутренние ссылки.


Скрытые ссылки через замыкания

Утечки часто возникают при сохранении инстанса в замыкании или глобальном хранилище:

function createPicker() {
  const fp = flatpickr("#date", {});

  return function getValue() {
    return fp.selectedDates;
  };
}

Функция getValue удерживает ссылку на fp, а значит весь объект календаря остаётся в памяти даже после удаления input.


Накопление инстансов в SPA

В React, Vue или аналогичных системах при неправильной интеграции создаются новые экземпляры Flatpickr при каждом рендере компонента.

Типичный анти-паттерн:

useEffect(() => {
  flatpickr(inputRef.current, {});
});

Каждый рендер создаёт новый календарь без уничтожения предыдущего. Это приводит к:

  • множественным DOM-календарям
  • дублированию обработчиков
  • росту памяти при каждом обновлении компонента

Неправильная работа с ref в UI-фреймворках

При повторном использовании DOM-узлов библиотека может повторно привязать события, если старый инстанс не был уничтожен. Это создаёт эффект “двойных кликов” и увеличивает количество активных listener-ов.


Незакрытые popover-элементы

Flatpickr создаёт элементы календаря вне основного контейнера input (обычно в body). Если инстанс не уничтожен, эти элементы продолжают существовать в DOM:

  • остаются невидимые узлы
  • продолжают удерживать ссылки на стили и события
  • увеличивают нагрузку на layout-дерево

Механика утечки через события

Flatpickr активно использует глобальные события:

  • click вне календаря
  • resize окна
  • keydown (Escape, стрелки)
  • scroll (в некоторых конфигурациях)

Если обработчики не удаляются, они продолжают вызываться даже после удаления UI, удерживая контекст инстанса.


Проблемы повторной инициализации

Повторный вызов flatpickr() на одном и том же элементе без уничтожения старого экземпляра приводит к наложению инстансов:

flatpickr("#date", {});
flatpickr("#date", {});

Последствия:

  • дублирование календарей
  • утечка памяти из-за старых ссылок
  • конфликт обработчиков событий

Слабые места при динамическом создании элементов

При создании элементов через innerHTML и последующей инициализации Flatpickr часто теряется контроль над предыдущими экземплярами:

container.innerHTML = `<input id="date">`;
flatpickr("#date", {});

При повторном обновлении контейнера старый input исчезает из DOM, но инстанс календаря может остаться в памяти, если не был уничтожен явно.


Утечки через кастомные плагины и callbacks

Flatpickr позволяет подключать кастомные плагины и обработчики:

  • onChange
  • onOpen
  • onClose
  • onDayCreate

Если внутри callback создаются внешние ссылки (например, на store или сервис), они могут удерживать инстанс даже после уничтожения DOM.

flatpickr("#date", {
  onChange: (selectedDates) => {
    globalStore.lastDate = selectedDates[0];
  }
});

Если globalStore хранит ссылки на инстансы, сборщик мусора не освобождает память.


Очистка ресурсов через destroy()

Корректное завершение жизненного цикла включает несколько операций:

  • удаление DOM календаря
  • удаление event listeners
  • разрыв внутренних ссылок
  • очистка состояния selectedDates
fp.destroy();
fp = null;

Обнуление переменной дополнительно снимает последнюю сильную ссылку.


Поведение в условиях виртуального DOM

В системах с виртуальным DOM (React, Vue) DOM-узлы могут переиспользоваться. Flatpickr при этом может не осознавать, что элемент был заменён логически, но не физически. Это приводит к ситуации:

  • старый инстанс продолжает слушать события
  • новый инстанс дублирует обработчики
  • календарь реагирует дважды на одно действие

Утечки через глобальные ссылки

Использование глобальных переменных для хранения инстансов приводит к накоплению объектов:

window.pickers.push(flatpickr("#date", {}));

Без очистки массива pickers все инстансы остаются в памяти, даже при удалении UI.


Особенности поведения в модальных окнах

Flatpickr часто используется внутри модалок. При закрытии модалки DOM может быть удалён, но:

  • popover календаря остаётся в body
  • обработчики продолжают работать
  • инстанс сохраняет ссылки на закрытую модалку

Это особенно заметно при повторном открытии модального окна.


Оптимизация управления инстансами

Управление жизненным циклом требует централизованного хранения ссылок:

  • создание инстанса при mount
  • уничтожение при unmount
  • отсутствие повторной инициализации без очистки

В противном случае количество активных календарей растёт линейно с количеством рендеров.


Диагностика утечек памяти

Типичные признаки:

  • рост памяти при каждом открытии календаря
  • увеличение количества event listeners
  • замедление работы интерфейса
  • появление “призрачных” календарей в DOM

Инструменты разработчика позволяют отслеживать:

  • Detached DOM nodes
  • retained objects
  • listener count на document

Сценарии наиболее частых ошибок

  • инициализация в цикле рендера
  • отсутствие destroy при смене страницы
  • хранение инстанса в глобальном состоянии
  • повторное применение Flatpickr к одному input без очистки
  • утечки через callback-функции с внешними ссылками

Поведение сборщика мусора

Garbage Collector в браузере освобождает память только при отсутствии сильных ссылок. Flatpickr-инстанс считается живым, пока:

  • существует DOM-элемент
  • зарегистрированы события
  • доступны замыкания с ссылками на объект

Любая из этих связей блокирует освобождение памяти.