Управление памятью в TypeScript: как работает сборщик мусора

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Память и сборщик мусора в TypeScript: что скрывается за кулисами?

Представьте, что вы строите дом. Вы закупаете кирпичи, цемент, арматуру — но если не убирать строительный мусор, скоро стройплощадка превратится в свалку. То же самое происходит в вашем коде на TypeScript. Вы создаёте объекты, массивы, замыкания — и если вовремя не освобождать память, приложение начинает тормозить, а потом и вовсе падает с ошибкой Out of Memory.

В этой статье мы разберём, как работает управление памятью в TypeScript и его главный инструмент — сборщик мусора. Вы узнаете, какие механизмы стоят за освобождением памяти, как избежать утечек и почему ваш код может «кушать» мегабайты даже после того, как вы перестали им пользоваться.

Поехали!

Как TypeScript управляет памятью? Основы

Начнём с важного: TypeScript — это не отдельный язык, а надстройка над JavaScript. Это значит, что память и сборщик мусора в TypeScript работают точно так же, как в JavaScript. TypeScript не добавляет новых механизмов управления памятью, но даёт нам инструменты (типы, интерфейсы), чтобы писать более безопасный код.

В JavaScript память делится на две основные области:

  • Стек (Stack) — хранит примитивы (числа, строки, булевы значения) и ссылки на объекты. Освобождается автоматически при выходе из функции.
  • Куча (Heap) — хранит объекты, массивы, функции. Именно здесь работает сборщик мусора.

Когда вы пишете:

const user = { name: 'Alice', age: 30 };

Переменная user хранится в стеке и содержит ссылку на объект в куче. Пока есть ссылка — объект жив. Как только ссылка исчезает — объект становится «мусором».

Сборщик мусора в TypeScript: как он работает?

Сборщик мусора (Garbage Collector, GC) — это автоматический процесс, который находит объекты, на которые больше нет ссылок, и освобождает занимаемую ими память. Современные движки (V8 в Chrome, SpiderMonkey в Firefox) используют сложные алгоритмы, но основа — это алгоритм пометок и очистки (Mark-and-Sweep).

Как это работает:

  1. GC начинает с «корней» — глобальных объектов, локальных переменных в функциях, которые выполняются.
  2. Он проходит по всем ссылкам от корней и помечает каждый достижимый объект.
  3. Все непомеченные объекты считаются мусором и удаляются.

Звучит просто, но на практике есть нюансы. Например, циклические ссылки:

function createCycle() {
    const obj1: any = {};
    const obj2: any = {};
    obj1.ref = obj2;
    obj2.ref = obj1;
    return 'done';
}

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

Типичные утечки памяти в TypeScript-приложениях

Даже с умным сборщиком мусора можно «наступить на грабли». Вот самые частые сценарии, которые приводят к утечкам памяти в TypeScript:

1. Забытые таймеры и обработчики событий

Одна из самых распространённых причин. Вы подписались на событие, а отписаться забыли:

class TimerComponent {
    private intervalId: number;

    start() {
        this.intervalId = window.setInterval(() => {
            console.log('Tick');
        }, 1000);
    }

    // Забыли вызвать stop() — таймер будет жить вечно!
}

Решение: всегда очищайте таймеры и отписывайтесь от событий в методе dispose или ngOnDestroy (для Angular).

2. Замыкания, которые держат ссылки

Замыкания — мощный инструмент, но они могут случайно удерживать огромные объекты:

function createHeavyClosure() {
    const hugeData = new Array(1000000).fill('data');

    return function() {
        console.log('Hello');
        // hugeData остаётся в памяти, даже если не используется
    };
}

const fn = createHeavyClosure();
// hugeData будет жить, пока жива fn

В таких случаях помогает обнуление ссылок или использование WeakMap для хранения данных.

3. Глобальные переменные и синглтоны

Если вы храните данные в глобальном объекте или синглтоне, они никогда не будут собраны сборщиком мусора:

class GlobalCache {
    private static cache: Map<string, any> = new Map();

    static set(key: string, value: any) {
        this.cache.set(key, value);
    }
    // Нет метода clear() — кэш растёт бесконечно
}

Решение: добавляйте методы очистки, используйте WeakMap или ограничивайте размер кэша.

4. DOM-ссылки в одностраничных приложениях

Если вы удаляете DOM-элемент, но где-то осталась ссылка на него в JavaScript, элемент не будет собран:

const elements: HTMLElement[] = [];

function addElement() {
    const div = document.createElement('div');
    document.body.appendChild(div);
    elements.push(div); // Ссылка сохраняется
}

// Даже если div удалён из DOM, он остаётся в памяти

Инструменты для борьбы с утечками: WeakMap и WeakSet

TypeScript (и JavaScript) предоставляют специальные коллекции, которые «дружат» со сборщиком мусора — WeakMap и WeakSet. Их ключи могут быть только объектами, и они не препятствуют сборке мусора.

Пример — кеширование данных для DOM-элементов:

const cache = new WeakMap<HTMLElement, any>();

function processElement(el: HTMLElement) {
    if (!cache.has(el)) {
        cache.set(el, { processed: true, timestamp: Date.now() });
    }
    return cache.get(el);
}

// Когда элемент удаляется из DOM и теряет все ссылки,
// запись в WeakMap автоматически удаляется сборщиком мусора

Это элегантное решение для оптимизации памяти в TypeScript без ручного управления.

Как отслеживать утечки памяти?

Самый надёжный способ — использовать инструменты разработчика в браузере:

  1. Откройте вкладку Memory в Chrome DevTools.
  2. Сделайте снимок кучи (Heap Snapshot).
  3. Выполните действия в приложении (например, откройте и закройте модальное окно).
  4. Сделайте второй снимок и сравните — если количество объектов раст

Блоги

Рекомендаций по книгам