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 .

![В любом случае, что такое связанный список? [Часть 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































