Как управление памятью, стек вызовов, потоки и цикл событий работают с движком 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"
  1. Движок узнает, что в нашей программе есть две функции.
  2. Запустите функцию calculate().
  3. Вставьте вычисление в стек вызовов и вычислите сумму.
  4. Выполните multiplyByTwo() функцию.
  5. Вставьте функцию multiplyByTwo в стек вызовов и выполните арифметическую операцию x * 2.
  6. При возврате значения извлеките multiplyByTwo() из стека и вернитесь к функции calculate().
  7. При возврате из функции 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. Теперь они должны быть довольны тем, что происходит под капотом, что, в свою очередь, поможет им писать лучший код.