Введение

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

Распределение памяти — куча и стек

Когда JavaScript запускается, он выделяет память в двух основных областях: куче и стеке.

  1. Куча памяти
    Куча памяти — это место, где хранятся динамически выделяемые объекты. Сюда входят объекты, созданные с использованием ключевого слова new, массивы и объекты, на которые ссылаются переменные или структуры данных, такие как связанные списки и деревья. Память кучи управляется сборщиком мусора, который автоматически освобождает память, когда объекты больше не используются.
  2. Память стека
    Память стека используется для вызовов функций и локальных переменных. Всякий раз, когда вызывается функция, в стек добавляется новый фрейм для хранения локальных переменных, аргументов функции и адресов возврата. По мере того, как функции вызываются и возвращаются, стек соответственно увеличивается и уменьшается. Память стека организована по принципу «последним поступил — первым вышел» (LIFO), то есть последний добавленный элемент удаляется первым. Память стека обычно выделяется и освобождается быстрее, чем память кучи.

Чтобы проиллюстрировать это, давайте рассмотрим несколько примеров:

function calculateSum(a, b) {
  const result = a + b;
  return result;
}

const x = 5;
const y = 10;
const sum = calculateSum(x, y);

В этом коде функция calculateSum и ее локальная переменная result хранятся в памяти стека, так как они создаются во время выполнения функции. Переменные x, y и sum также размещаются в памяти стека. Объект obj хранится в куче памяти.

// Creating a person object
const person = {
  name: 'John',
  age: 30
};

// Assigning person object to another variable
const anotherPerson = person;

// Modifying the name property of anotherPerson
anotherPerson.name = 'Jane';

// Accessing the name property of person
console.log(person.name); // Output: Jane

В этом примере у нас есть объект person со свойствами name и age. Когда мы присваиваем person anotherPerson с помощью оператора присваивания, он не создает новую копию объекта. Вместо этого anotherPerson теперь содержит ссылку на тот же объект в памяти.

И person, и anotherPerson являются ссылочными типами, хранящимися в куче. Переменные person и anotherPerson в стеке содержат адреса памяти (ссылки), указывающие на фактический объект в куче.

Когда мы изменяем свойство name для anotherPerson, это напрямую влияет на базовый объект. Поскольку и person, и anotherPerson ссылаются на один и тот же объект, доступ к свойству name объекта person после модификации даст нам обновленное значение.

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

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

Роль сборщика мусора

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

Давайте рассмотрим пример, чтобы понять, как работает сборщик мусора:

let obj1 = { name: 'John' };
let obj2 = { name: 'Jane' };

obj1 = null;

В этом фрагменте кода два объекта, obj1 и obj2, создаются в куче. Первоначально на оба объекта ссылаются. Однако, когда obj1 установлено в null, он больше не ссылается на объект { name: 'John' }. В этот момент сборщик мусора определяет, что на этот объект не осталось ссылок, что делает его пригодным для сборки мусора.

Жизненный цикл объекта и сборка мусора

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

Рассмотрим следующий пример:

function createObjects() {
  const obj1 = { name: 'John' };
  const obj2 = { name: 'Jane' };
  return [obj1, obj2];
}

const objects = createObjects();

В этом случае функция createObjects создает два объекта, obj1 и obj2, которые сохраняются в динамической памяти. Затем массив [obj1, obj2] возвращается и присваивается переменной objects. Пока переменная objects содержит ссылку на массив, объекты не подлежат сборке мусора.

Преимущества автоматического управления памятью

Одним из преимуществ сборки мусора в JavaScript является автоматическое управление памятью. Разработчикам не нужно явно освобождать память; сборщик мусора обрабатывает это за нас. Это снижает вероятность утечек памяти и упрощает управление памятью.

Заключение

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