hellojs.ru
Главная - JavaScript в работе - Сборщик мусора и утечки памяти

Сборщик мусора и утечки памяти

Размещено в категории "JavaScript в работе"
06.11.2024 / просмотров: 33 / комментариев: 0
Ресурсы:

Компилятор VS интерпретатор: ключевые отличия
Статья про сборщик мусора
Memory Inspector
Как избежать утечек памяти в JavaScript




JavaScript - это высокоуровневый язык программирования, так как многие базовые операции уже реализованы за нас на программном уровне.

Самый яркий пример - это работа с опертивной памятью компьютера.

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

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

Примеры таких программ - это Microsoft Word, Microsoft Excel и прочие установленные на компьютере программы.

JavaScript же выполняется с помощью программы-интерпретатора прямо внутри какой-то среды, например, внутри среды браузера.

В высокоуровневых языках все происходит условно автоматически: интерпретатор сам решает когда выделить память, как её использовать и как её освободить. Мы с вами не задумываемся в какой ячейке оперативной памяти лежит какой-то объект, который мы используем.

На этапе же освобождения памяти могут возникнуть проблемы, именно поэтому говорим "условно автоматически".

Сборщики мусора - это подпрограммы, которые отслеживают выделение и использование памяти компьютера, при необходимости автоматически очищающие её, в случае если какой-то код не используется.

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

Первый случай утечек памяти связан с глобальными переменными в случае, если в вашем коде не объявлена директива use strict и тогда можно создать переменную без её объявления

Код

function func() {
  elem = 'string';
}

И в данном случае ошибки не будет, поскольку это не строгий режим написания кода. Но это - неправильное поведение и в результате мы как бы создаём новую глобальную переменную, которая помещается в глобальный объект window, и эта глобальная переменная не может быть удалена сборщиком мусора.

Второй случай - это забытые вами или другим разработчиком таймеры. Они хранят ссылки на то, с чем вы должны работать.

Небольшой наглядный пример, в котором в переменную приходят какие-то данные, полученные как результат работы функции:

Код

const someRes = getData();

Есть также переменная, в которой лежит полученный со страницы по его классу элемент:

Код

const node = document.querySelector('.class');

И далее мы запустим setInterval(), содержащий внутри себя функцию с проверкой условия: что если элемент node есть на странице, то мы в него будем записывать данные из someRes

Код

setInterval(function() {
  if (node) {
  node.innerHTML = someRes;
  }
}, 1000);

Сейчас внутри setInterval есть ссылка и на данные из someRes и на элемент. И даже если сейчас удалить элемент из DOM-дерева, то он останется в памяти, так как ссылки на него все равно существуют внутри setInterval, который срабатывает каждую секунду, а это уже утечка памяти, поскольку память забивается не нужными нам данными.

Именно поэтому ненужные таймеры нужно останавливать.

Третий случай связан с обработчиками событий на не существующих элементах.

Часть элементов, на которые назначались обработчики событий, может исчезнуть во время работы сайта, в то время как ссылки на эти элементы остаются в обработчиках событий.

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

Чётвертый случай - это замыкания, содержащие большой объём данных, который не может быть удалён.

Пример:

Код

function outer() {
  const potentiallyHugeArray = [];
  return function inner() {
  potentiallyHugeArray.push('Hello');
  console.log('Hello!!!');
  }
}

const sayHello = outer();

В памяти будет храниться ссылка на массив potentiallyHugeArray, за счёт того, что он используется в возвращаемой функции.

Пятый случай - ссылки на DOM-элементы, которые мы очень часто сохраняем в различные переменные, одновременно с этим они существуют и в структуре под названием DOM, то есть это представление нашей вёрстки в виде объекта.

Когда мы удаляем какие-то объекты из DOM-дерева, то есть из вёрстки, то ссылка на него может оставаться в переменной javascript и этот элемент так и будет висеть в памяти.

Код

function createElement() {
  const div = document.createElement('div');
  div.id = 'test';
  return div;
}

Когда функция отработает, у нас будет создан новый элемент и помещён в возвращаемую переменную.

Далее мы можем создать новую переменную и в неё записать результат вызова нашей функции:

Код

const testDiv = createElement();

Теперь в переменной testDiv будет лежать блок с уникальным идентификатором. Мы можем поместить его на страницу:

Код

document.body.append(testDiv);

На каком-то этапе нам, к примеру, понадобится удалить этот блок. И допустим, что для этого у нас есть какая-то функция:

Код

function deleteElement() {
  document.body.removeChild(document.getElementById('testDiv'));
}

deleteElement();

В результате наш элемент будет удалён из DOM-дерева, но переменная testDiv сохранилась и она ссылается на тот блок div, который мы создали за счёт запуска функции и получения результата её работы в эту переменную. И таким образом образовалась утечка памяти.

Самый простой способ это исправить, это поместить эту строку

Код

document.body.append(testDiv);

внутрь функции createElement, а строку:

Код

const testDiv = createElement();

просто заменить на вызов функции:

Код

createElement();
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
Сайт управляется системой uCoz