Одним из ключевых источников утечек памяти при работе с Mapbox GL JS является неправильное управление экземпляром карты. Каждый объект карты создаёт набор WebGL-контекстов, воркеров, тайловых кешей и DOM-слоёв. При повторной инициализации без корректного уничтожения предыдущего экземпляра память продолжает накапливаться.
Корректное завершение работы карты должно включать явное удаление:
map.remove();
map = null;
Метод remove() освобождает:
Особенно критично это при SPA-архитектуре, где карта может пересоздаваться при переходах между маршрутами.
Дополнительно важно отслеживать собственные подписки:
function cleanup(map) {
map.off('click', onClickHandler);
map.off('move', onMoveHandler);
map.remove();
}
Mapbox GL JS активно использует событийную модель. Если обработчики создаются внутри функций рендера или эффектов без последующего удаления, они сохраняются в памяти даже после уничтожения UI.
Типичная ошибка:
map.on('move', () => {
updateUI();
});
Проблема заключается в невозможности снять такой обработчик без ссылки на функцию. Оптимальный подход:
function onMove() {
updateUI();
}
map.on('move', onMove);
map.off('move', onMove);
Также важно избегать привязки обработчиков к замыканиям, содержащим большие структуры данных.
Каждый source и layer в карте создаёт
дополнительные структуры в WebGL и памяти CPU. При динамическом
обновлении данных часто происходит накопление старых источников.
Правильная стратегия — явное удаление перед добавлением:
if (map.getSource('points')) {
map.removeLayer('points-layer');
map.removeSource('points');
}
Особое внимание требуется GeoJSON-источникам с частыми обновлениями. Вместо пересоздания источника предпочтительно обновление данных:
map.getSource('points').setData(newGeojson);
Это предотвращает фрагментацию памяти и лишние пересборки буферов.
GeoJSON остаётся одной из наиболее затратных по памяти структур в Mapbox GL JS. Основные проблемы возникают при передаче избыточной геометрии и свойств.
Практики снижения нагрузки:
Пример упрощения данных:
function simplifyFeature(feature) {
return {
type: 'Feature',
geometry: feature.geometry,
properties: {
id: feature.properties.id
}
};
}
При больших наборах данных предпочтительно использовать серверную генерацию тайлов вместо передачи полного GeoJSON.
Mapbox GL JS использует встроенный tile cache, который может занимать значительный объём памяти при работе с крупными картами.
Ключевые параметры:
minZoom,
maxZoom)maxTileCacheSize (если доступно в
конфигурации сборки)maxBoundsПример настройки:
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
minZoom: 3,
maxZoom: 16,
maxBounds: [
[60, 40],
[90, 55]
]
});
Чем уже диапазон работы карты, тем меньше тайлов остаётся в памяти.
Каждый слой в стиле увеличивает нагрузку на память GPU и CPU. Особенно затратны:
Оптимизация достигается:
Плохая практика:
for (const feature of features) {
map.addLayer({ id: feature.id, type: 'circle', source: 'points' });
}
Оптимальная стратегия:
map.addLayer({
id: 'points-layer',
type: 'circle',
source: 'points',
filter: ['==', 'type', 'city']
});
Векторный рендеринг требует больше вычислительных ресурсов, но даёт гибкость. Растровые тайлы менее затратны по памяти, но менее интерактивны.
При ограниченных ресурсах предпочтительно:
WebGL-контекст — один из самых чувствительных ресурсов. Его утечка приводит к критическому росту потребления памяти GPU.
Факторы, влияющие на стабильность:
map.remove()Также важно избегать создания карты в скрытых DOM-элементах с частыми пересозданиями.
Sprite-атласы и glyph-растры занимают значительную часть памяти. Их неправильная конфигурация приводит к дублированию загрузок.
Рекомендации:
Частые вызовы setData, setFilter,
setLayoutProperty приводят к постоянной переработке
буферов.
Оптимизация:
function debounce(fn, delay) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), delay);
};
}
const updateData = debounce((data) => {
map.getSource('points').setData(data);
}, 100);
Создание новой карты вместо обновления существующей — один из главных источников утечек.
Предпочтительно:
setStylenew Map()map.setStyle('mapbox://styles/mapbox/dark-v11');
Сложность сцены напрямую влияет на потребление памяти GPU:
При перегрузке сцены полезно:
Кластеризация уменьшает количество одновременно рендеримых объектов:
map.addSource('points', {
type: 'geojson',
data: geojson,
cluster: true,
clusterMaxZoom: 12,
clusterRadius: 50
});
Это снижает:
При обработке больших данных важно избегать блокировки основного потока. Mapbox GL JS уже использует Web Workers, но пользовательский код часто нарушает это правило.
Рекомендуется:
render-цикловЧастая проблема — сохранение ссылок на большие объекты в замыканиях обработчиков карты.
Пример проблемного кода:
function init(map) {
const hugeDataset = loadData();
map.on('render', () => {
process(hugeDataset);
});
}
Даже после удаления карты hugeDataset остаётся в
памяти.
Решение — явное управление областью видимости и очистка ссылок:
let hugeDataset = null;
function init(map) {
hugeDataset = loadData();
function handler() {
process(hugeDataset);
}
map.on('render', handler);
return () => {
map.off('render', handler);
hugeDataset = null;
};
}