Управление памятью в JavaScript выполняется автоматически и незаметно. Мы создаём примитивы, объекты, функции… Всё это занимает память.
Но что происходит, когда что-то больше не нужно? Как JavaScript понимает, что пора очищать память?
Достижимость
Основной концепцией управления памятью в JavaScript является принцип достижимости.
Если упростить, то «достижимые» значения – это те, которые доступны или используются. Они гарантированно находятся в памяти.
- Существует базовое множество достижимых значений, которые не могут быть удалены.Например:
- Локальные переменные и параметры текущей функции.
- Переменные и параметры других функций в текущей цепочке вложенных вызовов.
- Глобальные переменные.
- (некоторые другие внутренние значения)
- Любое другое значение считается достижимым, если оно доступно из корня по ссылке или по цепочке ссылок.Например, если в локальной переменной есть объект, и он имеет свойство, в котором хранится ссылка на другой объект, то этот объект считается достижимым. И те, на которые он ссылается, тоже достижимы. Далее вы познакомитесь с подробными примерами на эту тему.
В интерпретаторе JavaScript есть фоновый процесс, который называется сборщик мусора. Он следит за всеми объектами и удаляет те, которые стали недостижимы.
Простой пример
Вот самый простой пример:
// в user находится ссылка на объект
let user = {
name: "John"
};
Здесь стрелка обозначает ссылку на объект. Глобальная переменная user
ссылается на объект {name: "John"}
(мы будем называть его просто «John»). В свойстве "name"
объекта John хранится примитив, поэтому оно нарисовано внутри объекта.
Если перезаписать значение user
, то ссылка потеряется:
user = null;
Теперь объект John становится недостижимым. К нему нет доступа, на него нет ссылок. Сборщик мусора удалит эти данные и освободит память.
Две ссылки
Представим, что мы скопировали ссылку из user
в admin
:
// в user находится ссылка на объект
let user = {
name: "John"
};
let admin = user;
Теперь, если мы сделаем то же самое:
user = null;
…то объект John всё ещё достижим через глобальную переменную admin
, поэтому он находится в памяти. Если бы мы также перезаписали admin
, то John был бы удалён.
Взаимосвязанные объекты
Теперь более сложный пример. Семья:
function marry(man, woman) {
woman.husband = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: "John"
}, {
name: "Ann"
});
Функция marry
«женит» два объекта, давая им ссылки друг на друга, и возвращает новый объект, содержащий ссылки на два предыдущих.
В результате получаем такую структуру памяти:
На данный момент все объекты достижимы.
Теперь удалим две ссылки:
delete family.father;
delete family.mother.husband;
Недостаточно удалить только одну из этих ссылок, потому что все объекты останутся достижимыми.
Но если мы удалим обе, то увидим, что у объекта John больше нет входящих ссылок:
Исходящие ссылки не имеют значения. Только входящие ссылки могут сделать объект достижимым. Объект John теперь недостижим и будет удалён из памяти со всеми своими данными, которые также стали недоступны.
После сборки мусора: