JavaScript под капотом: цикл событий

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

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

Фото Марка Ст на Unsplash

Вы когда-нибудь сталкивались с неопределенными ошибками или пытались определить область действия переменной?

Отладка любой ошибки, не зная, как работает код, занимает действительно много времени.

В этом блоге я покажу, как на самом деле работают передовые концепции, например, event loopв отношении execution context, call stackи .callback queue

Отказ от ответственности — концепции невероятно наукоемки и взаимосвязаны, поэтому, пожалуйста, даже не моргайте!

JavaScript-движок

Движок Google V8 — хорошо известная иллюстрация движка JavaScript. Например, Chrome и Node.js используют движок V8. По сути, двигатель V8 состоит из двух частей —

  1. Стек вызовов: внутри него выполняется весь код. Он работает как структура данных Stack, что означает следование концепции LIFO (Last In First Out).
  2. Куча памяти: место, где происходит выделение памяти. В отличие от структуры данных кучи, это просто память.

Глобальный контекст выполнения или 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

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

Давайте пошагово посмотрим, как второй пример работает в среде выполнения.

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

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