Ресурсы: Компилятор 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();
просто заменить на вызов функции:
Добавлять комментарии могут только зарегистрированные пользователи.