Как управление памятью, стек вызовов, потоки и цикл событий работают с движком 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. Теперь они должны быть довольны тем, что происходит под капотом, что, в свою очередь, поможет им писать лучший код.