← Назад к вопросам

Как происходит взаимодействие с оперативной памятью в JS?

2.0 Middle🔥 162 комментариев
#JavaScript Core

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Управление памятью в JavaScript

JavaScript управляет памятью автоматически через сборщик мусора (garbage collector). Понимание того, как это работает, критично для написания производительного кода без утечек памяти.

1. Память: Stack vs Heap

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

// STACK - быстрая, маленькая память для примитивов
// Хранит: числа, строки, булевы значения, ссылки на объекты
// Размер: ограничен, обычно 1-8 MB
// Очистка: автоматически когда переменная выходит из scope

function example() {
  let a = 5;              // stack: a = 5
  let b = "hello";        // stack: b -> ссылка на строку в heap
  let c = true;           // stack: c = true
  // При выходе из функции: a, b, c удаляются из stack
}

// HEAP - медленная, большая память для объектов
// Хранит: объекты, массивы, функции
// Размер: большой, может быть гигабайты
// Очистка: через garbage collector (GC) когда нет ссылок

function createObject() {
  let obj = {         // stack: obj -> ссылка на heap
    name: 'John',     // heap: объект с данными
    age: 30
  };
  return obj;         // объект остаётся в heap
}

let user = createObject();  // stack: user -> ссылка на heap
// Объект в heap не удаляется, потому что на него есть ссылка

2. Garbage Collection (автоматическое управление памятью)

Когда нет ссылок на объект в памяти, он удаляется:

// Пример 1: Объект с одной ссылкой
let user = { name: 'John' };  // объект создан в heap
user = null;                  // ссылка удалена, объект может быть удален GC

// Пример 2: Массив с объектами
let users = [
  { name: 'John', age: 30 },
  { name: 'Jane', age: 25 }
];
// В heap находятся 3 объекта: массив + 2 объекта внутри

users = [];           // массив теперь пуст
// Но объект всё ещё существует! GC должен удалить его

users = null;         // теперь GC может удалить массив и всё содержимое

// Пример 3: Циклические ссылки (раньше были проблемы)
let obj1 = {};
let obj2 = {};
obj1.ref = obj2;      // obj1 -> obj2
obj2.ref = obj1;      // obj2 -> obj1 (циклическая ссылка)

obj1 = null;
obj2 = null;
// Современные GC справляются с этим, удалят оба объекта
// Но в старом IE было утечки памяти из-за циклических ссылок

3. Утечки памяти: как они происходят

// Утечка 1: Забытые таймеры
function setupTimer() {
  let data = new Array(1000000); // большой массив
  
  setInterval(() => {
    console.log(data.length);  // используем data
  }, 1000);
  // УТЕЧКА: setInterval остаётся в памяти, data не может быть удалена
  // Решение:
  // const intervalId = setInterval(...);
  // clearInterval(intervalId); когда нужно очистить
}

// Утечка 2: Event listeners не удалены
const button = document.querySelector('button');
button.addEventListener('click', function handler() {
  // УТЕЧКА: если handler содержит большие данные
  // и addEventListener не очищен, это утечка
});
// Решение:
// button.removeEventListener('click', handler);
// Или использовать once: { once: true }

// Утечка 3: Замыкания с большими данными
function createComponent() {
  let bigData = new Array(1000000);
  
  return {
    getData: () => bigData  // замыкание держит bigData в памяти
  };
}

let comp = createComponent();
// bigData остаётся в памяти даже если не используется
comp = null;  // только тогда bigData может быть удалена

// Утечка 4: Глобальные переменные (window)
window.largeArray = new Array(1000000);  // это в памяти ВЕЧНО
// Решение: избегай глобальных переменных

// Утечка 5: Detached DOM nodes
let element = document.querySelector('#myDiv');
element.parentNode.removeChild(element);  // элемент удалён из DOM
// Но если element всё ещё в памяти (переменная), это утечка
element = null;  // освобождаем

4. Как GC работает: Mark and Sweep

// Упрощённое объяснение алгоритма Mark and Sweep

// ШАГ 1: MARK (отметить живые объекты)
// GC начинает с корневых объектов (stack переменные, глобальные)
// И рекурсивно идёт по всем ссылкам

let root = { name: 'root' };
root.child = { name: 'child' };
root.child.grandchild = { name: 'grandchild' };

// В памяти GC создаёт граф:
// root -> child -> grandchild
// Все эти объекты помечены как "живые"

// ШАГ 2: SWEEP (удалить мёртвые объекты)
let orphan = { name: 'orphan' };
orphan = null;  // ссылка удалена
// orphan объект остался в памяти, но его никто не использует
// При следующем GC он не будет помечен как живой
// И будет удалён из памяти

// Пример временной шкалы:
// t=0: orphan создан
// t=1: orphan = null  (ссылка удалена)
// t=2: GC запускается -> не находит orphan в графе живых объектов
// t=3: orphan удаляется из памяти

5. Профилирование памяти в Chrome DevTools

// В Console можно проверить использование памяти
// performance.memory API

if (performance.memory) {
  console.log('Used JS heap: ', Math.round(performance.memory.usedJSHeapSize / 1024 / 1024), 'MB');
  console.log('Total JS heap: ', Math.round(performance.memory.totalJSHeapSize / 1024 / 1024), 'MB');
  console.log('Heap limit: ', Math.round(performance.memory.jsHeapSizeLimit / 1024 / 1024), 'MB');
}

// Пример:
// Used JS heap: 45 MB
// Total JS heap: 62 MB
// Heap limit: 2048 MB

6. Оптимизация использования памяти

// Проблема: создание большого числа объектов
for (let i = 0; i < 1000000; i++) {
  let obj = { x: i, y: i * 2 };  // 1 миллион объектов!
  // ПЛОХО: много операций allocation/deallocation
}

// Решение: переиспользуй объекты или используй типизированные массивы
const results = new Array(1000000);
for (let i = 0; i < 1000000; i++) {
  results[i] = { x: i, y: i * 2 };
}
// Или ещё лучше - используй Float64Array
const xValues = new Float64Array(1000000);
const yValues = new Float64Array(1000000);
for (let i = 0; i < 1000000; i++) {
  xValues[i] = i;
  yValues[i] = i * 2;
}
// TypedArrays занимают меньше памяти

// Проблема: хранение больших строк
let data = '';
for (let i = 0; i < 100000; i++) {
  data += 'some string'; // ПЛОХО: каждый раз создаётся новая строка
}

// Решение: используй array join
const parts = [];
for (let i = 0; i < 100000; i++) {
  parts.push('some string');
}
const data = parts.join('');  // ХОРОШО: один раз объединяем

7. WeakMap и WeakSet для временных ссылок

// Обычный Map держит объекты в памяти
const cache = new Map();
const key = { id: 1 };
cache.set(key, 'value');
key = null;  // объект всё ещё в памяти, потому что Map его держит!

// WeakMap не держит ссылки - объект может быть удалён
const weakCache = new WeakMap();
const key = { id: 1 };
weakCache.set(key, 'value');
key = null;  // объект может быть удалён GC, WeakMap не помешает

// Практический пример: кэш для DOM элементов
const elementsData = new WeakMap();
const button = document.querySelector('button');
elementsData.set(button, { clicks: 0 });
// Когда button удалён из DOM, данные тоже удалятся

8. Асинхронный код и утечки

// УТЕЧКА: Promise, которые никогда не resolve
function problematicAsync() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let bigData = new Array(1000000);
      // забыли resolve/reject
      // Promise остаётся в памяти, bigData тоже
    }, 10000);
  });
}

// РЕШЕНИЕ: всегда resolve/reject Promise
async function goodAsync() {
  try {
    const result = await fetch('/api');
    return await result.json();
  } catch (error) {
    console.error(error);
    throw error;  // важно!
  }
}

// УТЕЧКА: Observer, которые не unsubscribe
const observer = new IntersectionObserver((entries) => {
  let data = new Array(100000);
});
observer.observe(element);
// УТЕЧКА: observer никогда не удалён
// РЕШЕНИЕ:
// observer.unobserve(element);
// observer.disconnect();

Практические советы

  1. Всегда очищай таймеры и слушатели событий
const unsubscribe = addEventListener('click', handler);
// Когда не нужно:
unsubscribe();  // или removeEventListener
  1. Избегай циклических ссылок с большими данными
// ПЛОХО
obj.self = obj;  // циклическая ссылка

// ХОРОШО
// не держи циклические ссылки на себя
  1. Профилируй регулярно
// Chrome DevTools > Memory tab
// Бери snapshots и смотри на утечки
  1. Используй WeakMap для временных данных
const metadata = new WeakMap();
metadata.set(obj, { info: 'data' });

Понимание управления памятью в JavaScript - это ключ к написанию производительных приложений без утечек памяти, особенно для долгоживущих приложений (SPA, Node.js серверы).

Как происходит взаимодействие с оперативной памятью в JS? | PrepBro