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

В Части 1 этой статьи я дал краткий обзор того, как в целом работают языки программирования, и подробно обсудил конвейер движка V8. В этом посте будут рассмотрены еще несколько важных концепций, которые должен знать каждый программист на JavaScript и которые не связаны только с движком V8.
У любого программиста есть две основные проблемы: временная сложность и пространственная сложность. Часть 1 посвящена части скорости и оптимизации V8 для улучшения времени выполнения JavaScript, в этой части основное внимание будет уделено аспектам управления памятью.
Совет. Превратите повторно используемый код JS в общие компоненты с помощью Bit (GitHub). Bit помогает каждому создавать модульные приложения JavaScript , легко обменивайтесь компонентами между проектами и командой и создавайте лучше и быстрее.
Куча памяти

- Каждый раз, когда вы определяете переменную, константу, объект и т. Д. В своей программе javascript, вам нужно место для их хранения. Это место - не что иное, как куча памяти.
- Когда встречается оператор
var a = 10, в памяти назначается место для хранения значенияa. - Доступная память ограничена, и сложные программы могут иметь ряд переменных и вложенных объектов. Это делает очень важным разумное использование доступной памяти.
- В отличие от таких языков, как C, где нам нужно явно выделять и освобождать память, JavaScript предоставляет функцию автоматической сборки мусора. Как только объект / переменная выходит из контекста и больше не будет использоваться, его память освобождается и возвращается в пул свободной памяти.
- В V8 сборщик мусора называется Orinoco и имеет действительно эффективный процесс, описанный в этой статье.
Алгоритм Mark and Sweep

Этот простой и эффективный алгоритм используется для определения объектов, которые можно безопасно удалить из кучи памяти. Само название алгоритма описывает его работу; отмечать объекты как достижимые / недостижимые и подметать недостижимые.
Сборщик мусора периодически запускается с корневых или глобальных объектов и переходит к объектам, на которые они ссылаются, а затем к объектам, на которые ссылаются эти ссылки, и так далее. Затем все недостижимые объекты удаляются.
Утечки памяти
Хотя сборка мусора эффективна, разработчики не должны думать, что им не нужно беспокоиться об управлении памятью. Управление памятью - сложная процедура, и алгоритм не может решить, какая часть памяти не требуется.
Утечки памяти - это части памяти, в которых приложение нуждалось и использовалось в прошлом, и они больше не нужны, но их хранилище еще не возвращается в пул памяти.
Ниже приведены некоторые из распространенных ошибок, которые вызывают утечку памяти в вашем приложении.
Глобальные переменные: если вы продолжите создавать глобальные переменные, они будут оставаться на протяжении всего выполнения программы, даже если они не нужны. Если эти переменные являются глубоко вложенными объектами, тратится много памяти.
var a = { ... }
var b = { ... }
function hello() {
c = a; // this is the global variable that you aren't aware of.
}
Если вы попытаетесь получить доступ к переменной, которая не была объявлена ранее, вы создадите переменную в глобальной области видимости. В приведенном выше примере «c» - это та переменная / объект, которую вы неявно создали с помощью ключевого слова «var».
Слушатели событий. Это может произойти, когда вы создаете множество слушателей событий, чтобы сделать свой сайт интерактивным, или, может быть, просто для этих ярких анимаций и забываете удалить их, когда пользователь переходит на другую страницу вашей отдельной страницы. заявление. Теперь, когда пользователь перемещается между этими страницами, эти слушатели продолжают складывать.
var element = document.getElementById('button');
element.addEventListener('click', onClick)
Интервалы и тайм-ауты: при обращении к объектам внутри этих замыканий сборщик мусора никогда не очистит объекты, пока не будет очищено само замыкание.
setInterval(() => {
// reference objects
}
// now forget to clear the interval.
// you just created a memory leak!
Удаленные элементы DOM: Это похоже на утечку памяти глобальных переменных и очень распространено. Элементы DOM существуют в памяти графа объектов и дереве DOM. Этот сценарий лучше пояснить на примере.
var terminator = document.getElementById('terminate');
var badElem = document.getElementById('toDelete');
terminator.addEventListener('click', function() {memory
badElem.remove();
});
После того, как вы нажмете кнопку с id = ‘terminate’, toDelete будет удален из DOM. Но поскольку на него по-прежнему ссылается слушатель, выделенная для объекта память все еще используется.
var terminator = document.getElementById('terminate');
terminator.addEventListener('click', function() {
var badElem = document.getElementById('toDelete');
badElem.remove();
});
Теперь переменная badElem является локальной переменной, и после завершения операции удаления память может быть освобождена сборщиком мусора.
Стек вызовов
Стек - это структура данных, которая следует подходу LIFO (последний пришел - первым ушел) для хранения данных и доступа к ним. В случае движка JavaScript стек используется для запоминания местоположения последней выполненной команды в функции.
function multiplyByTwo(x) {
return x*2;
}
function calculate() {
const sum = 4 + 2;
return multiplyByTwo(sum);
}
calculate()
var hello = "some more code follows"
- Движок узнает, что в нашей программе есть две функции.
- Запустите функцию
calculate(). - Вставьте вычисление в стек вызовов и вычислите сумму.
- Выполните
multiplyByTwo() функцию. - Вставьте функцию
multiplyByTwoв стек вызовов и выполните арифметическую операцию x * 2. - При возврате значения извлеките
multiplyByTwo()из стека и вернитесь к функцииcalculate(). - При возврате из функции
calculate()извлекитеcalculateиз стека и продолжите выполнение кода.
Переполнение стека

Количество последовательных толчков, которые вы можете выполнить без выталкивания стека, зависит от размера стека. Если вы запустите ограничение и продолжите нажимать, это приведет к так называемому переполнению стека, и Chrome выдаст ошибку вместе со снимком стека, также называемым кадром стека.
Рекурсия: когда функция вызывает сама себя, это называется рекурсией. Эта концепция очень полезна в некоторых случаях, когда вы хотите сократить время выполнения алгоритма (временная сложность), но сложны для понимания и реализации.
В следующем примере базовый вариант никогда не выполняется, и функция lonely продолжает вызывать себя, не возвращаясь, что в конечном итоге приводит к переполнению стека.
function lonely() {
if (false) {
return 1; // the base case
}
lonely(); // the recursive call
}
Почему JavaScript однопоточный?
Поток представляет, сколько частей программы вы можете выполнять независимо и одновременно. Самый простой способ определить, является ли язык однопоточным или многопоточным, - это узнать, сколько у него стеков вызовов. У JS есть один, так что это однопоточный язык.
Так разве это не узкое место? Если я выполняю несколько операций, требующих много времени, также называемых блокирующими операциями, такими как HTTP-запросы, программе придется ждать ответа от каждой операции, прежде чем выполнять следующую.
Чтобы обойти эту проблему, нам нужен способ выполнять задачи асинхронно, даже если у нас есть один поток. Вот здесь-то и пригодится цикл обработки событий.
Цикл событий
До сих пор большинство вещей, о которых мы говорили, были включены в V8, но если вы поищете в базе кода V8 реализации таких вещей, как setTimeout или DOM, они не присутствуют в V8. Помимо механизма выполнения, JS состоит из того, что называется веб-API, которые предоставляются браузером для расширения JS.
Не думаю, что смогу объяснить эту концепцию так эффективно, как Филипс Робертс в этом видео.
Заключение
Еще многое предстоит сделать для создания языка программирования, и реализации продолжают меняться с годами. Я надеюсь, что эти два блога помогли вам стать лучше программистом JS и охватить странные части JS. Теперь вы должны привыкнуть к таким жаргонам, как «V8», «цикл событий», «стек вызовов» и т. Д.
Большинство студентов (как и я) начинают с нового фреймворка, прежде чем изучать ванильный JS. Теперь они должны быть довольны тем, что происходит под капотом, что, в свою очередь, поможет им писать лучший код.