JavaScript — это однопоточный язык программирования с асинхронным поведением.

Означает: JS выполняет задачи в одну строку одну за другой, не блокируя никакие другие задачи, которые не зависят друг от друга. Он не может выполнять несколько заданий одновременно.

Теперь, чтобы понять, JS использует управление памятью Heap and Stack.

Память стека используется для хранения данных фиксированного размера, размер которых известен во время компиляции (например, строки, числа, логические и другие примитивные типы), и следует методу LIFO для доступа к данным, хранящимся в памяти стека.

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

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

Теперь у нас есть стек вызовов функций, который мы, возможно, уже видели в той или иной форме в виде трассировки стека при выводе ошибки.

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

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

Теперь, наконец, чтобы понять цикл событий.

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

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

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

Всякий раз, когда вызывается асинхронная функция, она отправляется в API браузера. Это API, встроенные в браузер. На основании команды, полученной из стека вызовов, API начинает свою работу. После завершения асинхронного поведения функция обратного вызова помещается обратно в очередь событий, а цикл событий проверяет, пуст ли стек или нет, а в пустом стеке новая функция извлекается из очереди и помещается в стек, выполняет ее и выталкивает после завершения.

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

Давайте разберемся с примерами.

function fxn1(){
  console.log('I'm First.');
}
function fxn2(){
  console.log('I'm Second.');
}
function fxn3(){
  console.log('I'm Third.');
}

Теперь давайте вызовем эти функции

fxn1();
fxn2();
fxn3();

Таким образом, мы получаем соответствующий результат,

I'm First.
I'm Second.
I'm Third.

Итак, вот что произошло: во-первых, эти функции fxn1, fxn2, fxn3 перечислены в очереди событий, затем цикл событий проверяется на стек вызовов, он пуст, поэтому он отправил fxn1 в стек вызовов, и он был выполнен и выскочил из стека, затем цикл событий продолжает проверять очередь событий и стек событий, пока они оба не опустеют для выполнения всех работает по порядку, как указано в очереди.

Итак, если fxn2 имеет функцию асинхронного поведения, скажем, fxn2 был изменен на:

 function fxn2(){
  setTimeout(()=>{
    consloe.log('I'm Second.');
},100)
}

Если мы вызовем fnx1(), fxn2(), fxn3() по порядку, то получим следующий вывод:

I'm First.
I'm Third.
I'm Second.

Случилось так, что у нас есть fxn1,fxn2, fxn3 в очереди событий, затем они помещаются в стек соответственно один за другим. Но во время выполнения fnx2 он получает асинхронную функцию для обработки, стек вызовов отправляет его в веб-API, и он ожидает указанный интервал, т. е. 100 мс, и после завершения ожидания функция обратного вызова перемещается в очередь событий, поэтому она находится за fnx3. Теперь обратный вызов вызывается только после выполнения fxn3, поэтому он печатается позже.

Заключение

Таким образом, очередь событий, стек вызовов, цикл событий и веб-API работают вместе для выполнения функции. Функции перечислены в очереди, цикл событий проверяется и помещается в стек, если он пуст, и выполняется, и извлекается. Если обнаружен асинхронный вызов, веб-API вызывается для обработки и помещается в очередь для окончательного выполнения. Этот цикл продолжается до тех пор, пока не будут выполнены все функции в очереди событий.

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