JavaScript под капотом: цикл событий
Давайте освоим JavaScript, изучив его функционирование с нуля

Вы когда-нибудь сталкивались с неопределенными ошибками или пытались определить область действия переменной?
Отладка любой ошибки, не зная, как работает код, занимает действительно много времени.
В этом блоге я покажу, как на самом деле работают передовые концепции, например, event loop
в отношении execution context
, call stack
и .callback queue
Отказ от ответственности — концепции невероятно наукоемки и взаимосвязаны, поэтому, пожалуйста, даже не моргайте!
JavaScript-движок
Движок Google V8 — хорошо известная иллюстрация движка JavaScript. Например, Chrome и Node.js используют движок V8. По сути, двигатель V8 состоит из двух частей —
- Стек вызовов: внутри него выполняется весь код. Он работает как структура данных Stack, что означает следование концепции LIFO (Last In First Out).
- Куча памяти: место, где происходит выделение памяти. В отличие от структуры данных кучи, это просто память.

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

В зависимости от того, где будет выполняться ваш код, будет определено, где this
он находится. Например, в Node.js он указывает на отдельный глобальный объект, тогда как в браузере он указывает на window
объект.

Стек вызовов (или стек выполнения функций)
JavaScript — это single-threaded
, как вы уже слышали. Но что это на самом деле означает?
Это означает, что движок JavaScript содержит только один call stack
или function execution stack
.
- Как мы знаем, всякий раз, когда компилятор впервые исследует ваш код, JS-движок просит компилятор создать
Global Execution Context
илиGEC
, а также поместить его в файлCall Stack
. - Весь ваш код выполняется один за другим в ,
Global Execution Context
и он выделяет память для определения функции или объявления переменной и сохраняет ее там. - Но когда какой-либо вызов функции найден, создается
Functional Execution Context
илиFEC
для выполнения кода функции, а затем он добавляется в начало файлаcall stack
. - Интерпретатор удаляет функцию из
call stack
каждый раз, когда функция завершается. Функция завершается — когда она достигает конца своей области видимости или оператора return. - Наконец, выполнение всего вашего кода
GEC
удаляется из файлаCall Stack
.
Не волнуйтесь, давайте продемонстрируем это на примере.
function f1{
console.log('f1');
}
f1();
console.log('end');

Шаг 2: — В нашем примере будет выполнена 1-я строка — f1
. Будет выделена память f1
и сохранено его определение.

Шаг 3: — Во 2-й строке вызывается функция. Для вызова этой функции будет создан объект Function Execution Context
or FEC
, который будет храниться поверх файла Call Stack
.

Шаг 4: — Теперь целое f1()
будет выполняться построчно и после завершения выполнения будет удалено из файла Call Stack
.

Шаг 5: —console.log('end')
Затем будет выполнена последняя строка и на консоли будет напечатано «end». Наконец, выполнение всего вашего кода Global Execution Context
будет удалено из файла Call Stack
.

Как JS управляет асинхронными задачами?
JavaScript, как мы все знаем, является синхронным , однопоточным языком (одна задача за раз) и единственным потоком, который call stack
немедленно выполняет все, что попадает внутрь него.
Но что, если нам нужно что-то выполнить через 5 секунд? Можем ли мы надеть это внутри call stack
?
Нет, мы не можем. Потому call stack
что нет таймера. Но как мы можем это сделать?
Здесь вступает в действие среда выполнения JavaScript .

Это может быть разбито сердцем, если я скажу вам сейчас , что это не часть JavaScript , setTimeout()
хотя все события DOM являются частями веб-API , которые предоставляют доступ к JavaScript Engine для использования всех его свойств в глобальном контексте выполнения (GEC) через глобальный объект . Эти веб-API называются асинхронными .console.log()
window
Что такое очередь обратного вызова?
Очередь задач, называемая «очередью обратных вызовов» или «очередью задач», выполняется после завершения текущих задач стека вызовов. Задачи, зарегистрированные в веб-API, перемещаются из веб-API в очередь обратного вызова.
Очередь обратного вызова работает как структура данных очереди, что означает, что задачи обрабатываются в порядке FIFO (первым пришел, первым вышел), в отличие от стека вызовов, что означает, что задачи обрабатываются в том порядке, в котором они были добавлены в очередь.

Что такое цикл событий?
Цикл событий JavaScript добавляет задачу из очереди обратного вызова в стек вызовов в порядке FIFO, как только стек вызовов становится пустым.
Цикл событий блокируется, если в стеке вызовов в данный момент выполняется какой-либо код, и не будет добавлять дополнительные вызовы из очереди, пока стек снова не станет пустым. Это связано с тем, что код JavaScript выполняется в режиме выполнения до завершения.
Давайте разберемся с приведенными выше концепциями на примере.
- Сначала
Global Execution Context
создается для нашего кода внутри нашегоcall stack
иGEC
выполняет наш код построчно. GEC
выполняет 1-ю строку и печатает «Пуск» на нашей консоли.- Выполняя 2-ю строку,
setTimeout()
будет вызван веб-API, а затемsetTimeout()
предоставлен доступ к функции таймера. Затем вы сможете установить5000ms
в качестве времени задержки. - Когда вы передаете
callBack()
функцию черезsetTimeout()
,callBack()
она будет зарегистрирована как обратный вызов через веб-API. - А затем
GEC
выполняет 1-ю строку, а также печатает «Конец» на консоли. - Когда весь код будет выполнен,
GEC
он будет удален из нашего файлаcall stack
. - После
5000 millisecond
этогоcallBack()
функция, зарегистрированная в файлеweb API
, перемещается внутрь файлаcall Back Queue
. Event loop
помещаетcallBack()
функцию в состояние,call Stack
когда она выполнила всю свою работу. И, наконец,callBack()
функция выполняется и выводит «Call Back» в консоль.








function f1() {
console.log('f1');
}
function f2() {
console.log('f2');
}
function main() {
console.log('main');
setTimeout(f1, 0);
f2();
}
main();
Если вы думаете, что «f1» будет напечатано перед «f2», то вы ошибаетесь. Это будет -
main
f2
f1
Обсуждаемый механизм JavaScript подходит для любой функции обратного вызова или запроса API.
Давайте пошагово посмотрим, как второй пример работает в среде выполнения.

- Сначала
GEC
будет создан внутри ,call stack
а затем код будет выполняться построчно вGEC
. Он хранит все определения функций в куче памяти . - Когда
main()
вызывается, создается aFunction Execution Context (FEC)
, а затем он попадает в стек вызовов. После этого весь кодmain()
функции будет выполняться построчно. - У него есть консольный журнал для печати слова main. Таким образом,
console.log('main')
выполняется и выходит из стека. - Имеет место API
setTimeout()
браузера. Функция обратного вызова помещает его в очередь обратного вызова. Но в стеке выполнение происходит как обычно, поэтомуf2()
попадает в стек. Консольный журнал выполненияf2()
. Оба выходят из стека. - И тогда
main()
тоже выскакивает из стека. - Цикл событий распознает, что стек вызовов пуст, а в очереди есть функция обратного вызова. Таким образом, функция обратного вызова
f1()
будет помещена в стек методомevent loop
. Исполнение начинается. Журнал консоли выполняется иf1()
также выталкивается из стека. И, наконец, в стеке и очереди больше ничего нет для дальнейшего выполнения.
Это моя практика и заметка. Если вы нашли это полезным, пожалуйста, покажите свою поддержку, щелкнув значок хлопка ниже. Вы можете следить за мной на Medium .