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

Shepherd.js — это библиотека для создания интерактивных пошаговых туров по веб-приложению. Как и любая динамическая библиотека, она требует внимательного подхода к управлению памятью. Неправильное использование Shepherd.js может привести к утечкам памяти, особенно при частом создании и уничтожении туров, динамических элементов DOM и подписок на события.


Создание туров и управление их жизненным циклом

Основной объект Shepherd — Tour. Каждое создание нового тура выделяет ресурсы, включая DOM-элементы подсказок (Shepherd.Tour.addStep) и обработчики событий.

const tour = new Shepherd.Tour({
  defaultStepOptions: {
    scrollTo: true,
    cancelIcon: { enabled: true }
  }
});

tour.addStep({
  id: 'intro',
  text: 'Добро пожаловать!',
  attachTo: { element: '.header', on: 'bottom' }
});

Ключевой момент: каждый созданный тур нужно правильно завершать. Использование tour.complete() или tour.cancel() освобождает связанные ресурсы. После завершения рекомендуется удалять ссылки на объекты тура:

tour.complete();
tour = null; // Очистка ссылки для сборщика мусора

Без этой очистки объекты тура могут оставаться в памяти, особенно если туры создаются динамически при переходах между страницами.


Управление DOM-элементами

Shepherd.js создает элементы подсказок (.shepherd-step) и добавляет их в DOM. При множественном создании туров без удаления старых шагов эти элементы накапливаются.

Правильная очистка DOM:

tour.steps.forEach(step => step.destroy());

Метод destroy() удаляет элементы подсказок и отвязывает события, предотвращая накопление узлов в DOM. Не стоит полагаться на автоматическую очистку при удалении тура — destroy() нужно вызывать вручную.


Обработка событий и слушателей

Каждый шаг Shepherd.js может регистрировать обработчики событий:

step.on('show', () => console.log('Шаг показан'));
step.on('hide', () => console.log('Шаг скрыт'));

Опасность: если шаги уничтожаются без удаления слушателей, функции остаются в памяти, создавая утечки.

Решение: использовать метод off перед уничтожением:

step.off('show');
step.off('hide');
step.destroy();

Или использовать tour.on и затем tour.off для глобальных событий тура:

tour.on('complete', () => console.log('Тур завершен'));
// ...
tour.off('complete');

Работа с динамическими элементами

Если шаг прикреплен к динамически создаваемому элементу (attachTo.element), важно учитывать, что при удалении элемента DOM подсказка не уничтожается автоматически. Это приводит к “висячим” элементам, которые удерживают память.

Рекомендации:

  • Проверять существование элемента перед показом шага:
if (document.querySelector('.dynamic-element')) {
  tour.show('dynamic-step');
}
  • Уничтожать шаги при удалении элемента:
const dynamicElement = document.querySelector('.dynamic-element');
dynamicElement.addEventListener('remove', () => {
  step.destroy();
});

Рекомендации по предотвращению утечек

  1. Создавать один тур на страницу при возможности. Частое создание/уничтожение множества туров увеличивает риск утечек.
  2. Всегда вызывать destroy и очищать слушатели для каждого шага перед удалением туров.
  3. Удалять ссылки на объекты туров и шагов, чтобы сборщик мусора мог их освободить.
  4. Контролировать привязку к DOM — избегать шагов на элементах, которые часто перерисовываются или удаляются.
  5. Использовать tour.cancel() для корректного завершения активного тура при переходах между страницами или компонентами SPA.

Пример безопасного цикла жизни тура

let tour;

function initTour() {
  tour = new Shepherd.Tour({ defaultStepOptions: { cancelIcon: { enabled: true } } });

  tour.addStep({
    id: 'step1',
    text: 'Первый шаг',
    attachTo: { element: '.header', on: 'bottom' }
  });

  tour.on('complete', cleanupTour);
  tour.on('cancel', cleanupTour);
}

function cleanupTour() {
  tour.steps.forEach(step => {
    step.off(); 
    step.destroy();
  });
  tour.off();
  tour = null;
}

Такой подход гарантирует, что при завершении или отмене тура все элементы, события и ссылки на объекты освобождаются, предотвращая накопление памяти.


Мониторинг утечек

Для SPA и динамических приложений рекомендуется периодически проверять использование памяти через инструменты браузера (Chrome DevTools → Memory). Можно фиксировать количество шагов и туров перед и после их создания и удаления. Любое неосвобожденное увеличение памяти сигнализирует о потенциальных утечках, связанных с несвоевременным вызовом destroy и off.


Управление памятью в Shepherd.js требует системного подхода: контролировать создание и уничтожение туров, шагов, событий и DOM-элементов. Правильная очистка предотвращает постепенное накопление памяти, особенно в долгоживущих веб-приложениях.