Удаление слушателей

Система событий в Google Maps JavaScript API построена вокруг менеджера google.maps.event, который обеспечивает привязку обработчиков к объектам карты, маркерам, оверлеям и DOM-событиям. Архитектура ориентирована на явное управление жизненным циклом подписок, поскольку объекты карты часто существуют длительное время и могут динамически пересоздаваться.

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


Регистрация слушателей и идентификатор подписки

Базовый механизм подписки использует функции:

  • google.maps.event.addListener
  • google.maps.event.addDomListener (устаревший подход)
  • google.maps.event.addListenerOnce

Пример регистрации:

const map = new google.maps.Map(document.getElementById("map"), {
  center: { lat: 40, lng: -70 },
  zoom: 8,
});

const listener = google.maps.event.addListener(map, "click", (event) => {
  console.log(event.latLng);
});

Возвращаемый объект listener содержит внутренние данные о подписке, включая ссылку на источник события и функцию-обработчик. Именно он используется для удаления.


Базовое удаление слушателя

Основной механизм удаления реализован через:

  • google.maps.event.removeListener(listener)
google.maps.event.removeListener(listener);

Удаление происходит строго по ссылке, возвращённой при создании подписки. Попытка удалить обработчик без сохранённого идентификатора делает операцию невозможной.

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


Жизненный цикл слушателя

Слушатель существует до тех пор, пока выполняются одновременно три условия:

  1. Объект-источник события существует (например, map, marker)
  2. Подписка не удалена вручную
  3. Не выполнена одноразовая подписка (addListenerOnce)

Одноразовые слушатели автоматически удаляются после первого вызова:

google.maps.event.addListenerOnce(map, "idle", () => {
  console.log("карта загружена");
});

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

Для полного освобождения всех привязок используется:

  • google.maps.event.clearInstanceListeners(object)
google.maps.event.clearInstanceListeners(map);

Этот вызов удаляет все события, связанные с конкретным экземпляром (например, картой или маркером), включая пользовательские и системные обработчики.

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


Удаление событий по типу

В API предусмотрена возможность очистки всех обработчиков конкретного события:

  • google.maps.event.clearListeners(instance, eventName)
google.maps.event.clearListeners(map, "click");

Этот метод удаляет все callback-функции, привязанные к указанному событию, но не затрагивает остальные события объекта.


Управление коллекциями слушателей

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

const listeners = [];

listeners.push(
  google.maps.event.addListener(map, "click", onClick),
  google.maps.event.addListener(map, "zoom_changed", onZoom)
);

Удаление:

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

Такой паттерн снижает риск утечек памяти, особенно при динамическом создании и уничтожении компонентов.


Взаимодействие с маркерами и оверлеями

Маркер является частым источником накопления слушателей:

const marker = new google.maps.Marker({
  position: { lat: 50, lng: 30 },
  map,
});

const clickListener = google.maps.event.addListener(marker, "click", () => {
  console.log("marker clicked");
});

Удаление маркера не гарантирует автоматическую очистку всех внешних ссылок на обработчики, если они сохранены в переменных. Поэтому явное удаление:

google.maps.event.removeListener(clickListener);
marker.setMap(null);

При большом количестве маркеров применяется стратегия централизованного менеджмента подписок.


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

В контексте Google Maps JavaScript API утечки чаще всего возникают из-за:

  • сохранённых ссылок на listener, которые никогда не удаляются
  • повторного создания карты без очистки предыдущих подписок
  • замыкания, удерживающего большие объекты через callback
  • накопления слушателей при повторной инициализации компонентов интерфейса

Особенно критичны ситуации, когда обработчик замыкает map, marker и внешние состояния одновременно, что удерживает объекты в памяти даже после удаления карты с DOM.


Очистка при уничтожении карты

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

listeners.forEach((l) => google.maps.event.removeListener(l));
google.maps.event.clearInstanceListeners(map);
map = null;

Такой подход разрывает все связи между системой событий и объектом карты.


Особенности removeListener в разных версиях API

В современных версиях Google Maps JavaScript API метод removeListener остаётся основным способом удаления одиночных подписок, тогда как более старые подходы через clearListeners и clearInstanceListeners используются для массовой очистки.

Важно учитывать, что DOM-ориентированные методы (addDomListener) постепенно считаются устаревшими, и их поведение в современных приложениях заменяется стандартными браузерными addEventListener/removeEventListener.


Паттерны безопасного управления подписками

Распространённые архитектурные подходы:

Контейнер подписок

class MapEventManager {
  constructor() {
    this.listeners = [];
  }

  add(listener) {
    this.listeners.push(listener);
    return listener;
  }

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

Привязка к жизненному циклу компонента

В приложениях с компонентной архитектурой слушатели привязываются к этапам:

  • создание компонента → регистрация событий
  • обновление → частичная переинициализация
  • уничтожение → полная очистка через removeListener и clearInstanceListeners

Сложные сценарии удаления (динамические слои)

При работе с динамическими слоями карты (например, фильтры маркеров или обновляемые данные) создаются временные подписки:

function attachTemporaryListener(map) {
  const l = google.maps.event.addListener(map, "bounds_changed", () => {
    console.log("bounds updated");
  });

  setTimeout(() => {
    google.maps.event.removeListener(l);
  }, 5000);
}

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


Сравнение методов очистки

Метод Назначение Область действия
removeListener удаление одного слушателя конкретная подписка
clearListeners очистка событий по типу одно событие объекта
clearInstanceListeners полная очистка объекта все события объекта

Поведение при удалении объекта-источника

Если объект карты или маркера уничтожается без явной очистки слушателей, поведение зависит от сборщика мусора JavaScript. Однако наличие замыканий и сохранённых ссылок в обработчиках может препятствовать освобождению памяти.

Поэтому явное управление жизненным циклом слушателей остаётся обязательной частью архитектуры при работе с Google Maps JavaScript API.


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

Частый источник проблем:

const data = new Array(1000000).fill("x");

google.maps.event.addListener(map, "click", () => {
  console.log(data.length);
});

Даже после удаления слушателя через removeListener, при сохранении ссылки на callback в другом месте или при циклических зависимостях объект data может оставаться в памяти.


Поведение removeListener при повторном вызове

Повторное удаление одного и того же listener:

google.maps.event.removeListener(listener);
google.maps.event.removeListener(listener);

Второй вызов является безопасным и не приводит к ошибкам, но не имеет эффекта, так как связь уже разорвана.


Управление событиями в масштабируемых приложениях

В крупных системах с использованием Google Maps API применяется централизованный event-layer, который:

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

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