От 0 до 1- Как JavaScript работает под капотом

Многие разработчики кодируют на JavaScript с тех пор, как Брендан Эйх разработал JavaScript в 1995 году. Если кто-то действительно хочет изучить JavaScript, необходимо понимать JavaScript. Вы должны знать историю JavaScript, какую проблему он решает, и как JavaScript работает за сценой. Эта справочная информация поможет вам, когда вы будете кодировать и изучать новые возможности.

В этой статье мы узнаем немного об истории JavaScript, о том, как JavaScript работает за кулисами? Что такое среда выполнения? Какую роль играет движок JavaScript в выполнении кода? Что такое контекст выполнения? Что такое Scope, TDZ и многое другое?

Вы готовы 😉

История JavaScript

Прежде всего, давайте обсудим необходимость JavaScript, чтобы вы имели представление о том, почему вы используете JS и какова его цель.

На заре веб (да, каменный век 😉) существовали способы создания веб страниц (HTML был выпущен в 1993 году), но не было эффективного способа манипулировать ими. В наши дни это стало возможным благодаря DOM (Document Object Model — объектная модель документа — будет обсуждаться позже). Поэтому статические страницы обычно были неинтерактивными. Это была основная причина, по которой необходимо было разработать какой-то язык, чтобы дать им жизнь.

В 1995 году Брендан Эйх выступил вперед и разработал первую версию JavaScript всего за 10 дней, это не опечатка, я действительно имел в виду 10 дней. Тогда он назывался LiveScript (классное название, но мне больше нравится JavaScript 😉). В то время JavaScript был похож на инопланетную технологию и был быстро принят. Сообщество продолжало расширяться, он становился все лучше и лучше, и, конечно, JavaScript, который мы используем, — это монстр по сравнению с теми днями.

Сначала он был разработан для Netscape 2 и стал стандартом ECMA-262 в 1997 году. В 1997 году был выпущен ECMAScript 1, который впервые был поддержан в Internet Explorer 4. Затем в 1998 году вышел ECMAScript 2, а в 1999 году — ECMAScript 3. Четвертая редакция языка немного задержалась и была выпущена в 2008 году, но не смогла выйти на рынок. Пятая основная редакция, ECMAScript 5, была выпущена в 2009 году. Эта версия использовалась некоторое время, и в 2015 году была выпущена последняя версия ECMAScript 6, которая в настоящее время широко поддерживается во всех основных браузерах, за исключением Internet Explorer. Здесь вы можете узнать больше об истории вашего любимого языка.

Здесь ECMAscript

ECMAScript — это стандарт JavaScript, предназначенный для обеспечения совместимости веб-страниц в различных браузерах. Он стандартизирован Ecma International в соответствии с документом ECMA-262. В основном он стандартизирует, какой код JavaScript что дозирует, поэтому JavaScript ведет себя одинаково во всех браузерах.

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

JavaScript на простом английском языке

  • JavaScript является интерпретируемым языком, что означает, что, как и Java (никакого сходства с JavaScript, кроме названия), он не требует от нас компиляции перед отправкой в браузер для выполнения, интерпретатор может взять исходный код JS и выполнить его за нас.

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

  • JavaScript — это неблокирующий язык программирования или скриптовый язык, который не блокирует предстоящие задачи, если программа занимает много времени, с помощью Web API, очереди обратных вызовов и цикла событий. На данный момент вам может показаться, что это утверждение о неблокируемости противоречит вышеприведенному утверждению об однопоточности. Но через некоторое время мы обсудим все эти концепции в деталях. Вам просто нужно быть со мной в течение следующих нескольких минут.

  • JavaScript является динамически типизированным языком, что означает, что var может хранить любой тип данных, например int, string или object, в отличие от статически типизированных языков, таких как C и C++, в которых мы должны явно указывать тип переменной datatype имя_переменной.

Среда выполнения

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

Runtime Environment — это, по сути, фабрика, куда загружается сырой код JavaScript и с помощью смеси веб-интерфейсов, библиотек и объектов выполняется. Надеемся, это дало вам некоторое представление о среде выполнения. Если вы все еще сомневаетесь, не волнуйтесь, в ближайшее время мы обсудим компоненты среды выполнения и их роль в выполнении JS. Вам просто нужно держаться вместе со мной, и в конце этого руководства у вас будет очень четкое представление о JS.

В контексте веб-браузера среда выполнения состоит из следующих компонентов:

  • JavaScript Engine
    • Стека вызовов или стека выполнения
    • Куча
  • Веб-апи (такие как fetch, setTimeout, DOM, File API)
  • Очередь обратных вызовов
  • Цикл событий

Среда выполнения зависит от контекста, в котором вы выполняете код JavaScript. В зависимости от контекста среда выполнения может выглядеть несколько иначе. Например, в среде NodeJs код JavaScript не имеет доступа к Web API, поскольку они являются функцией веб-браузеров. Однако основные функции будут одинаковыми или похожими в любом контексте.

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

Две наиболее важные части веб-браузера — это движок JavaScript и движок рендеринга.

Механизм рендеринга

Механизм рендеринга рисует содержимое на экране. Blink — это движок рендеринга, который отвечает за весь конвейер рендеринга, включая деревья DOM, стили, события и интеграцию с V8. Он анализирует деревья DOM, разрешает стили и определяет визуальную геометрию элементов на экране.

JavaScript Engine

Цель JavaScript-движка — перевести исходный код, который пишут разработчики, в машинный код (двоичный), который может понять процессор. Таким образом, это место или, можно сказать, фабрика, где исходный JavaScript загружается, разбирается и преобразуется в машинный код, который может быть понят процессором.

JavaScript engine компилирует и исполняет исходный код JavaScript в родной машинный код. Таким образом, движок JavaScript — это место, где исходный код JS загружается, разбирается, интерпретируется и выполняется в конце в виде двоичного кода. Каждый крупный производитель браузеров разработал свой собственный движок JavaScript, который в основном работает одинаково. Например, Chrome имеет V8, Safari имеет JavaScriptCore, а Firefox использует SpiderMonkey.

В этой статье мы сосредоточимся на движке V8. Прочитайте, что такое движок V8 со слов Google;

V8 — это высокопроизводительный JavaScript и WebAssembly движок Google с открытым исходным кодом, написанный на C++. Он используется в Chrome и в Node.js, среди прочих. Он реализует ECMAScript и WebAssembly и работает на Windows 7 или более поздней версии, macOS 10.12+ и системах Linux, использующих процессоры x64, IA-32, ARM или MIPS. V8 может работать автономно или может быть встроен в любое приложение на C++.

Работа JavaScript Engine(V8)

Первая версия V8 была выпущена и использована командой Google Chrome в 2010 году, когда у них возникли проблемы с отображением Google Maps. Позже они улучшили ее со временем и в 2017 году выпустили другую версию V8, которая используется в браузере Chrome в настоящее время.

Текущая версия была построена на этой модели;

[Изображение скопировано из этой замечательной статьи Удая Хиварале].

Давайте разберем это по порядку;

Движок V8 загружает исходный код JS и передает его в Baseline Compiler, который обрабатывает и компилирует код в легкий байткод.

Этот байткод передается интерпретатору, который, по сути, является алгоритмом IntelliSense, который знает, какой JS-код что дозирует. Он интерпретирует байткод в двоичный код, понятный процессору.

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

Это окончательная модель двигателя V8. Но так было не всегда. После многих обновлений и переделок появилась именно эта оптимизированная версия движка.

Это базовая модель функционирования JavaScript-движка Chrome JavaScript Engine(V8). Другие производители браузеров используют различные движки JavaScript. Но функционируют они примерно одинаково.

Как код JavaScript работает в среде выполнения

В отличие от других языков программирования, JavaScript является однопоточным во время выполнения, что в просторечии означает, что он может выполнять только один фрагмент кода одновременно. Код выполняется последовательно, поэтому, когда один процесс занимает больше времени, он блокирует весь последующий код, ожидающий выполнения. Поэтому иногда вы можете увидеть предупреждение Page Unresponsive. Обычно это происходит, когда во время выполнения возникает бесконечный цикл кода. И он продолжает выполняться до тех пор, пока не будут израсходованы все ресурсы процессора.

Рассмотрим этот вечный цикл;

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

Когда встречается такой бесконечный цикл или огромная программа JavaScript, выполнение кода после него блокируется. Поскольку JavaScript является однопоточным по своей природе и выполняется по одному фрагменту за раз. Таким образом, бесконечные циклы продолжают выполняться снова и снова, пока у системы не закончатся ресурсы. И нам приходится видеть диалог «Страница не реагирует».

Благодаря современным браузерам они используют отдельные потоки выполнения JavaScript для разных вкладок, иначе наш браузер был бы заморожен, если бы в одной вкладке на одной странице встретилась такая тяжелая программа или бесконечный цикл. Современные веб-браузеры обычно используют отдельные потоки выполнения для разных вкладок или один поток выполнения для одного домена (один и тот же сайт на нескольких вкладках). Chrome использует политику «один процесс для одного сайта», поэтому, если несколько доменов были открыты на разных вкладках, все они перестанут работать. Вкладки других доменов (сайтов) будут продолжать работать нормально.

Давайте обсудим движок JavaScript более подробно. Движок JavaScript состоит в основном из двух компонентов: Callstack и Heap.

Куча

Куча — это неструктурированная память, в которой хранятся переменные и объекты во время выполнения программы. Затем куча, также известная как «куча памяти», очищается во время сборки мусора. Я не буду говорить об этом подробно, вы можете ознакомиться с этой замечательной статьей.

Стек вызовов или стек выполнения

Нас интересует стек вызовов, который, по сути, является хранилищем данных LIFO (первым вошел, последним вышел), где хранятся текущие контексты выполнения во время работы программы. О том, что такое контекст исполнения, мы подробно поговорим чуть позже. Каждая запись в стеке вызовов называется кадром стека. Стековый кадр содержит информацию об исполняемом контексте, такую как его объект аргумента, адрес возврата, локальные переменные и т.д.

Что такое контекст выполнения?

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

Во время выполнения контекста исполнения специальный код разбирается парсером, переменные и объявления сохраняются в памяти (VO), генерируется исполняемый байткод, который затем преобразуется в двоичный код, который исполняется.

Во время выполнения создаются два вида контекстов выполнения; 

  • Глобальный контекст исполнения (GEC)
  • Функциональный контекст исполнения (FEC)

Глобальный контекст исполнения (GEC)

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

Это основной контекст выполнения по умолчанию, который инкапсулирует все функциональные контексты выполнения.

Для любого файла сценария существует только один GEC.

Функциональный контекст выполнения (FEC)

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

Поскольку в любом скрипте (файле JavaScript) обычно имеется несколько функций, каждая функция выполняется в своем собственном контексте выполнения, и в одной программе может быть несколько функциональных контекстов выполнения.

Как создается контекст исполнения?

Как мы уже знаем, у контекста исполнения есть две основные задачи: он подготавливает сценарий и затем выполняет его. Поэтому мы попытаемся понять контекст выполнения в двух фазах;

  • Фаза создания
  • Фаза выполнения

Фаза создания

Фаза создания любого контекста выполнения состоит из трех основных этапов.

  • Создание объекта переменной
  • Создание цепочки областей видимости
  • Присвоение значения this.
Фаза создания: Создание объекта переменной (VO)

Для GEC создается объект переменной, который по сути является контейнером памяти, хранящим свойства для всех переменных и объявлений функций и хранящим ссылки на них. Когда переменная встречается в глобальном контексте выполнения, ее свойство добавляется к объекту переменных и инициализируется (в случае определения с помощью var) значением по умолчанию undefined.

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

Это означает, что еще до начала выполнения кода переменные и объявления функций доступны для использования. Благодаря hoisting. Мы обсудим подъем в деталях через некоторое время.

В случае FEC (функциональный контекст выполнения) создается не VO, а объект типа массива, называемый объектом аргументов. Все аргументы, полученные функцией, хранятся в этом массивоподобном объекте.

Подъем в JavaScript

JavaScript перемещает все переменные, объявления функций и классов в верхнюю часть их области видимости. Этот процесс в JavaScript называется hoisting.

Говоря простым языком, перед выполнением кода JavaScript сохраняет все переменные, функции и объявления классов в контейнере памяти, называемом объектом переменных, в верхней части области видимости, этот процесс известен как Hoisting в JavaScript. По сути, благодаря hoisting мы можем получить доступ к функциям и переменным еще до их объявления.

Подъем функций

Некоторые разработчики JavaScript предпочитают определять все свои функции в верхней части сценария, а затем вызывать их в конце. Приведенный ниже код будет работать нормально благодаря hoisting, хотя мы вызываем функцию до того, как она была объявлена.

Подъем переменных

Объявления переменных также поднимаются. Переменные, объявленные с помощью ключевого слова var, поднимаются и инициализируются значением по умолчанию undefined. То есть, если вы попытаетесь обратиться к переменной, объявленной с помощью var, вы не получите ошибку, но значение будет undefined.

Если вы объявляете переменную с ключевым словом let или const, их объявление поднимается, но не инициализируется значением по умолчанию undefined. Вот почему вы получите uncaught ReferenceError, когда мы попытаемся получить к нему доступ до объявления.

Здесь очень важно обсудить одну вещь. Вас могут спросить об этом на собеседовании.

Начиная с вершины области видимости, до полной инициализации переменной, эта переменная находится в Temporal Dead Zone (TDZ). Вы всегда должны пытаться получить доступ к переменной вне ее TDZ. Если вы попытаетесь обратиться к переменной изнутри ее TDZ, вы получите ошибку ReferenceError. Здесь возникает вопрос, где начинается TDZ и где она заканчивается.

Из приведенного ниже кода вы узнаете, что временная мертвая зона начинается в начале блока кода (в верхней части области видимости) и заканчивается при инициализации. Переменные, объявленные с помощью let и const, следуют той же схеме.

В случае переменных, объявленных с помощью var, сценарий немного другой. И виновник торжествует. Как мы уже знаем, переменные, объявленные var, поднимаются и инициализируются одновременно со значением по умолчанию undefined. И мы также знаем, что TDZ заканчивается, когда переменной было присвоено значение. 

По этой причине var ведет себя немного иначе, чем let и const. См. этот code;

Надеюсь, с TDZ вам все понятно. Давайте вернемся к подъему.
Есть одно строгое правило, что hoisting работает только для утверждений, но не для выражений. Взгляните на этот код;

Это потому, что мы присваиваем выражение функции переменной в качестве значения. Как мы все знаем, переменные, объявленные с ключевым словом let, поднимаются, но при обращении к ним в пределах временной мертвой зоны выдает ReferenceError. Это происходит потому, что их значение не инициализируется во время подъема, а TDZ заканчивается только после полной инициализации переменной.

Время викторины: Вот небольшой вопрос для вас? Каким будет результат, если мы используем var вместо letting в приведенном выше примере кода. Сможете ли вы угадать, будет ли выдана ошибка или вывод функции будет записан в консоль?

Я уверен, что вы угадали правильно. В этом случае ошибка будет заключаться в том, что myFunc не является функцией, поскольку на данном этапе значение переменной myFunc будет undefined из-за Hoisting. Поэтому вызов undefined() приведет к ошибке.

Фаза создания: Создание цепочки Scope

Область видимости — это механизм в JavaScript, который определяет, какой фрагмент кода откуда доступен. Когда переменная, функция или класс объявляются в сценарии, они имеют некоторый адрес и некоторые границы. За этими границами она недоступна. Область видимости отвечает на многие вопросы, например, откуда код может быть доступен, а откуда нет? 

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

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

Всякий раз, когда переменная или функция вызывается где-либо, движок начинает искать ее внутри локальной области видимости, в которой она была вызвана. Если она не найдена, она ищет родительские области видимости (Lexical Scoping) по очереди, от внутренней (локальной) к внешней (глобальной). Если переменная не была найдена в локальной и всех родительских областях видимости, включая глобальную корневую область видимости, то механизм выдает ошибку.

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

Случай 1: Переменные, объявленные внутри функции, могут быть доступны из любого места внутри этой функции, за исключением временной мертвой зоны.

Случай 2: Функция, объявленная внутри функции, имеет доступ ко всему из своей родительской функции и родительской функции родителя вплоть до глобальной области видимости. Это называется Lexical Scoping. Эта концепция привела к появлению в JavaScript так называемых замыканий.

Когда функция внутри другой функции вызывается вне ее контекста, то есть вне родительской функции, в которой она была объявлена, но она все еще имеет доступ к переменным родительской функции даже после завершения выполнения родительской функции, это ассоциативное явление называется замыканием. Это очень важная концепция, и вам определенно стоит ознакомиться с ней здесь.

Родительская функция не имеет доступа ни к чему, что объявлено внутри ее дочерних функций. Область видимости подобна одностороннему зеркалу, то есть вы можете посмотреть наружу, но теперь вы можете посмотреть внутрь.

[Изображение скопировано из этой замечательной статьи Виктора Икечукву на freeCodeCamp].

Как видно из этого изображения, функция second имеет доступ ко всем диапазонам, включая свой собственный local диапазон, диапазон функции first, и global диапазон. Но функция first имеет доступ только к своей локальной области видимости и глобальной области видимости. Видно, что она не имеет доступа к области видимости функции second.

На этом второй шаг создания контекста исполнения завершается.

До этого момента был создан объект Variable, создана цепочка областей видимости. Давайте обсудим третий и последний шаг.

Этап создания: Установка значения this.

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

Значение this различно, в зависимости от того, в каком контексте оно используется. Взгляните на эти случаи;

Случай 1: В глобальном контексте выполнения this ссылается на глобальный объект окна в случае браузеров. Попробуйте записать «this» в консоль внутри GEC, и вы увидите объект window. Здесь следует заметить одну интересную вещь. Когда вы определяете переменную или функцию внутри GEC, они сохраняются как свойство объекта window. Это означает, что эти два утверждения эквивалентны;

И в консоль будет выведено true;

Случай 2: Внутри FEC новый объект this не создается, а вместо этого он ссылается на контекст, к которому принадлежит. Например, в данном случае this ссылается на объект глобального окна. Поскольку эта функция объявлена в GEC, она будет ссылаться на глобальный объект window.

Случай 3: В случае с объектами использование this внутри методов ссылается не на глобальный объект, а на сам объект. Посмотрите на этот пример;

Случай 4: Внутри функций конструктора, this ссылается на только что созданный объект, когда вызывается с ключевым словом new, как это сделано здесь;

На этом фаза создания Execution Context завершена. До этого момента все было сохранено в VO, цепочка областей видимости создана, а значение this находится на месте.

Фаза выполнения

Сразу после создания контекста исполнения. Движок JavaScript начинает выполнение созданного контекста. Но Variable Object в настоящее время содержит все объявления этого конкретного контекста исполнения, но их значение не определено. А вы уже знаете, что мы не можем работать с undefined. Поэтому движок JavaScript снова просматривает VO и передает их исходные значения. После этого код разбирается парсером, транспилируется в облегченный байткод, который затем преобразуется в двоичный код(01), который затем исполняется.

Callstack JavaScript (с точки зрения контекста выполнения)

Прежде всего, скрипт загружается в браузер, движок JavaScript создает глобальный контекст исполнения (Global Execution Context). Этот GEC отвечает за обработку всего кода, который не находится внутри какой-либо функции.

Изначально этот GEC является активным контекстом выполнения. Когда какая-либо функция встречается в этом GEC, создается новый функциональный контекст исполнения для выполнения этой функции. Этот FEC содержит всю информацию о выполнении этой функции и все, что помогает в ее выполнении. Этот вновь созданный контекст выполнения размещается прямо над GEC. Этот процесс повторяется для каждого вызова функции, FEC добавляется и накапливается в так называемом Callstack.

Контекст выполнения, находящийся на вершине, выполняется первым, и как только этот контекст выполнен (что-то возвращено), он выводится из стека. Следующий контекст становится активным и начинается его выполнение. Этот процесс повторяется до тех пор, пока в стеке не останется последний GEC. Он выполняется в конце, и считается, что скрипт выполнен в этот момент.

Заключение

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

Рассмотрим эту программу внутри файла скрипта;

Прежде всего, этот файл .js загружается в браузер и передается движку JavaScript для выполнения. Движок создает глобальный контекст выполнения для этого файла, который обрабатывает выполнение корня этого файла сценария, всего, что не находится внутри функции. Этот GEC размещается в нижней (или верхней) части Execution Stack. Глобальный контекст исполнения создается в течение двух фаз: фазы создания и фазы исполнения.

Переменная name="Victor" хранится в объекте переменных (VO) GEC и инициализируется значением по умолчанию undefined (hoisting).

Затем для всех этих функций first, second и third в VO GEC добавляется свойство, а в качестве значения хранится ссылка на эти функции. Вы знаете, что это такое 😉.
 
После установки VO GEC, создается цепочка scope, и значение this устанавливается (window).

Теперь начинается выполнение. Но значение переменной name все еще undefined в VO. А мы не можем работать с неопределенным значением, верно? Поэтому JS-движок снова просматривает VO и передает исходное значение переменной name, которое равно Victor. Теперь мы можем продолжить работу в нашей программе.

Прежде всего, вызывается функция first. JS-движок создает функциональный контекст исполнения (Functional Execution Context) для обработки ее выполнения. Этот FEC помещается поверх Global Execution Context, образуя Callstack или Execution Stack. В течение некоторого времени этот FEC является активным, так как мы уже знаем, что Execution Context на вершине Callstack является активным. Переменная a = "Hi!" хранится в FEC, а не в GEC.

В следующем операторе функция first вызывает функцию second. Создается еще один FEC, который помещается поверх FEC функции first. Теперь этот FEC активен. Переменная b="Hey!" сохраняется в FEC.

Затем функция second вызывает функцию third, аналогично создается FEC и помещается на вершину стека Execution. Переменная c = "Hello!" хранится в FEC.

Пока что стек выполнения выглядит следующим образом;

И записывает Hello Victor в консоль. Но подождите! Откуда взялся Виктор? Переменная name не определена в функции third. Вы правильно догадались, Scope Chain. Функция third ищет переменную name внутри своей локальной области видимости, но не находит ее. Из-за того, что называется Lexical Scoping, она также имеет доступ к своей родительской области видимости, глобальной области видимости. Движок JavaScript ищет name в глобальной области видимости и находит ее.

Когда функция third выполнит все свои задачи, ее FEC будет извлечен из стека выполнения (Callstack).

Затем первый FEC под ней становится активным контекстом и начинает выполнение. Записывает Hey! Victor в консоль’ и выходит из стека выполнения. Теперь последний FEC этой программы становится активным, записывает Hi! Victor в консоль. После выполнения всех операторов он уничтожается и выходит из стека вызовов.

В стеке выполнения снова остается только GEC. Так как ему больше нечего исполнять, он также вылетает из стека.

Надеюсь, этот пример прояснил большинство ваших сомнений. Мы рассмотрели первый компонент среды выполнения JavaScript. Надеюсь, вы нашли его полезным.

До сих пор мы узнали

  • Немного истории JavaScript

  • JavaScript Runtime, который, по сути, является специальной средой, предоставляемой браузером или контекстом, в котором мы выполняем наш код. Эта среда предоставляет нам объекты, API и другие компоненты, чтобы наш код мог взаимодействовать с внешним миром и выполняться.

  • Компоненты среды выполнения: движок JavaScript, Web APIs, очередь обратных вызовов и Eventloop.

  • Движок JavaScript, который состоит из Callstack или Execution Stack и Heap.

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

  • Типы контекста исполнения: глобальный контекст исполнения (GEC) и функциональный контекст исполнения (FEC).

  • Фаза создания Контекста Исполнения, которая завершается тремя фазами; создание VO, построение цепочки Scope и установление значения этого.

  • Фаза выполнения контекста исполнения, мы узнали, как создается GEC после загрузки скрипта и как каждая функция создает свой FEC. Они продолжают накладываться друг на друга, пока не вернут что-то и не выйдут из стека.

  • Scoping и Temporal Dead Zone. Мы узнали, как функции могут получать доступ к объявлениям из родительской области видимости с помощью Lexical Scoping. Мы также вкратце обсудили TDZ.

Недостаток однопоточной природы JS

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

Но что, если текущая выполняемая задача занимает так много времени, что, если наш текущий контекст выполнения запрашивает некоторые данные с сервера (медленный сервер), определенно это займет некоторое время. В этом случае очередь Callstack застрянет, поскольку JavaScript выполняет только одну задачу за раз. Все контексты выполнения, которые будут выполняться следующими, будут ждать, пока наш текущий виновный контекст выполнения не будет решен. Как мы можем справиться с подобным поведением. Как мы можем запланировать выполнение некоторых задач или припарковать некоторые дорогостоящие задачи, чтобы наше приложение продолжало работать? Как мы можем сделать наш синхронный JavaScript асинхронным? 

Именно здесь на помощь приходят Web API, очередь обратных вызовов и Eventloop. Я планировал обсудить все эти концепции в одном руководстве, но объем получился больше, чем я ожидал.

Что дальше?

Теперь нам остались три других компонента JavaScript Runtime,

  • Веб-интерфейсы
  • Очередь обратных вызовов
  • Контур событий

Я буду выпускать еще одно подобное руководство по этим оставшимся компонентам. Если вы хотите получить уведомление о следующей части, следуйте за мной 😉.

Это был первый компонент JavaScript runtime, Работа движка JavaScript при выполнении кода. Я постарался упростить все концепции, чтобы даже абсолютные новички смогли их понять. Надеюсь, вы смогли разобраться и нашли это полезным.

Примечания и мотивы

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

  • Потрясающая статья Как JavaScript и JavaScript движок работают в браузере и ноде? от Uday Hiwarale.
  • Обширная статья о JavaScript Execution Context от Виктора Икечукву.
  • Элегантное руководство по JavaScript Runtime Environment от Gemma Croad.

Заключительные слова

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

До тех пор оставайтесь в безопасности и старайтесь обезопасить других.

До скорой встречи💓

Оцените статью
devanswers.ru
Добавить комментарий