Удаление слушателей и объектов

В экосистеме Google Maps JavaScript API событийная модель строится вокруг подписок на события через google.maps.event.addListener и связанные методы. Каждое добавление обработчика возвращает объект типа MapsEventListener, который становится ключевым элементом последующего удаления.

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

Ключевая ошибка при работе с событиями — отсутствие сохранения ссылки на подписку:

const listener = map.addListener('click', (e) => {
  console.log(e.latLng.toString());
});

Возвращаемый объект необходимо сохранять, поскольку именно он используется для удаления:

google.maps.event.removeListener(listener);

Если ссылка не сохранена, точечное удаление становится невозможным, и единственным вариантом остаётся массовая очистка.

Удаление конкретного слушателя

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

google.maps.event.removeListener(listener);

Этот вызов полностью отсоединяет callback от источника события. Важно учитывать, что после удаления объект слушателя становится невалидным, и повторное использование ссылки не имеет смысла.

Очистка всех слушателей объекта

Для полного сброса всех событий у конкретного экземпляра применяется:

google.maps.event.clearInstanceListeners(map);

Метод удаляет все обработчики, зарегистрированные на объекте, включая внутренние подписки API. Это особенно полезно при уничтожении карты или переинициализации компонента.

Очистка слушателей по типу события

Более точечный вариант — удаление всех обработчиков конкретного события:

google.maps.event.clearListeners(map, 'click');

Такой подход сохраняет остальные события, но полностью отключает выбранный тип взаимодействия.


Удаление объектов карты

В Google Maps JavaScript API геометрические и графические объекты не удаляются напрямую через remove() как в DOM. Вместо этого используется механизм привязки к карте через setMap(null).

Маркеры

Удаление маркера:

marker.setMap(null);

После этого объект перестаёт отображаться и освобождает ресурсы визуализации. Однако ссылка на объект остаётся в памяти JavaScript до тех пор, пока на него есть внешние ссылки.

Линии, полигоны и окружности

Все объекты, наследующие MVCObject, используют одинаковый механизм:

polyline.setMap(null);
polygon.setMap(null);
circle.setMap(null);

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

GroundOverlay и InfoWindow

Для наложений и информационных окон используется аналогичный подход:

overlay.setMap(null);
infoWindow.close();

Важно учитывать, что InfoWindow дополнительно управляется состоянием открытия, поэтому корректная очистка включает закрытие перед удалением ссылки.


Полное удаление и разыменование объектов

Простое скрытие через setMap(null) не гарантирует освобождение памяти. Объект остаётся в памяти, если:

  • сохранён в массиве
  • замкнут в closure
  • привязан к DOM-обработчику
  • используется в состоянии фреймворка

Корректная деактивация требует разыменования:

marker.setMap(null);
marker = null;

В структурах данных:

markers.forEach(m => m.setMap(null));
markers.length = 0;

Удаление слушателей при уничтожении объектов

Комбинированные сценарии включают и объекты, и их события:

const listener = marker.addListener('click', handler);

// при удалении
google.maps.event.removeListener(listener);
marker.setMap(null);
marker = null;

Если обработчиков много, используется централизованный реестр:

const listeners = [];

listeners.push(
  map.addListener('click', onClick),
  marker.addListener('dragend', onDragEnd)
);

listeners.forEach(l => google.maps.event.removeListener(l));
listeners.length = 0;

Очистка при повторной инициализации карты

При пересоздании карты типичная проблема — наложение старых слушателей и объектов на новые экземпляры.

Корректный порядок очистки:

  1. удалить все слушатели карты
  2. удалить слушатели объектов
  3. снять объекты с карты
  4. очистить ссылки
google.maps.event.clearInstanceListeners(map);

markers.forEach(m => {
  google.maps.event.clearInstanceListeners(m);
  m.setMap(null);
});

markers.length = 0;
map = null;

Утечки памяти и скрытые ссылки

В Google Maps JavaScript API утечки чаще всего возникают не из-за самой карты, а из-за внешнего кода:

Closure-захват

function createMarker(data) {
  const marker = new google.maps.Marker({ map, position: data.pos });

  marker.addListener('click', () => {
    console.log(data); // удерживает data в памяти
  });

  return marker;
}

Даже после удаления маркера data может оставаться в памяти из-за замыкания.

Хранение в глобальных структурах

window.mapState.markers.push(marker);

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


Работа с DOM-событиями внутри API

Помимо событий карты, часто используются DOM-события:

const btn = document.getElementById('btn');

const handler = () => console.log('click');
btn.addEventListener('click', handler);

Удаление:

btn.removeEventListener('click', handler);

При интеграции с картой важно синхронизировать очистку DOM-слушателей с удалением объектов карты.


Поведение в SPA и повторных рендерах

В одностраничных приложениях карта может пересоздаваться при навигации. Основная проблема — повторная подписка без очистки предыдущей.

Типовой шаблон очистки при unmount:

function destroyMap() {
  google.maps.event.clearInstanceListeners(map);

  markers.forEach(m => m.setMap(null));
  markers = [];

  map = null;
}

Если используется компонентный подход, очистка должна происходить строго в lifecycle-хуке уничтожения компонента.


Массовое удаление через паттерн менеджера объектов

При большом количестве объектов применяется централизованный менеджер:

class MapManager {
  constructor(map) {
    this.map = map;
    this.markers = [];
    this.listeners = [];
  }

  addMarker(marker) {
    this.markers.push(marker);
  }

  addListener(listener) {
    this.listeners.push(listener);
  }

  clear() {
    this.listeners.forEach(l =>
      google.maps.event.removeListener(l)
    );

    this.markers.forEach(m => m.setMap(null));

    this.listeners = [];
    this.markers = [];
  }
}

Такой подход снижает риск «висящих» объектов и неконтролируемых подписок.


Особенности внутренних подписок MVCObject

Многие объекты API наследуют MVCObject, что означает наличие внутренней системы наблюдения свойств. Очистка таких объектов требует внимания к:

  • привязанным bind-свойствам
  • listeners на свойства
  • внешним ссылкам на get/set наблюдения

При массовом удалении предпочтительно использовать clearInstanceListeners, так как он охватывает внутренние подписки API, недоступные через пользовательские ссылки.


Типовые ошибки при удалении объектов и слушателей

  • удаление маркера без удаления listener
  • потеря ссылки на listener и невозможность точечного удаления
  • повторное добавление обработчиков без очистки старых
  • хранение объектов карты в долгоживущих структурах
  • отсутствие очистки при смене маршрута в SPA
  • использование setMap(null) без обнуления ссылок

Каждая из этих ошибок приводит к накоплению объектов и росту потребления памяти даже при визуально «пустой» карте.