Эта статья является второй частью серии из четырех частей, посвященных асинхронному Javascript. Вы можете просмотреть Часть 1 здесь.

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

Цикл событий

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

Попробуем лучше понять цикл событий на примере

Выход:

4
3
1
5
2

Если вас смущает порядок выполнения, читайте дальше. Как видите, у нас есть три объявления функций, а также ряд операторов и вызовов функций. Пойдем построчно в выполнение программы. В начале программы создается и запускается цикл обработки событий Javascript. Цикл событий сначала проверяет, есть ли в стеке вызовов какая-либо функция. В настоящее время наш стек вызовов выглядит так:

|             |
|             |
|             |
|             |
|             |
|_____________|
   
  Call stack

Поскольку стек вызовов пуст, программа продолжает выполнение в строке 1, где определена функция sayOne. Поскольку это всего лишь определение, программа просто сохраняет код функции в переменной с именем sayOne и продолжает работу. На этом этапе цикл обработки событий снова проверяет, есть ли функция в стеке вызовов. Поскольку стек вызовов все еще пуст, программа переходит к следующей строке, которая равна 6. Здесь повторяются те же шаги действий, где сохраняется определение функции sayTwo, а затем цикл событий проверяет стек вызовов снова. Затем программа переходит к строке 10, где те же шаги повторяются для функции sayThree.

Затем программа переходит к строке 14, где она впервые встречает оператор. Помните, что стек вызовов на этом этапе все еще пуст. Перед выполнением оператора console.log для вывода «4» на консоль цикл обработки событий проверяет, пуст ли стек вызовов. Поскольку это так, программа продолжает выполнение и выводит 4 на консоль. Затем программа переходит к строке 15, где видит, что была вызвана функция sayOne. Следовательно, он немедленно добавляет эту функцию в стек вызовов, который теперь выглядит следующим образом.

|             |
|             |
|             |
|             |
|  sayOne()   |
|_____________|
   
  Call stack

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

|             |
|             |
|             |
|  sayThree() |
|  sayOne()   |
|_____________|
   
  Call stack

Прежде чем перейти к строке 3 функции sayOne, цикл событий запускается еще раз, чтобы проверить, не пуст ли стек. Когда он узнает, что это так, он в этот момент выполняет два действия. Сначала он извлекает функцию из верхней части стека, а затем проверяет, совпадает ли текущая выполняемая функция с ней или нет. Если он был таким же, он продолжает выполнение текущей функции. Если они не совпадают (а в нашем случае это не так), программа приостанавливает выполнение текущей функции и переходит к выполнению самой верхней функции (которой в данном случае является sayThree ). Итак, в строке 11 перед выполнением оператора console.log цикл обработки событий еще раз проверяет наличие непустого стека, извлекает самую верхнюю функцию, обнаруживает, что она такая же, как текущая выполняемая функция, и, таким образом, возобновляет свой код. Вызывается строка 11 (в результате чего оператор console.log выводит на консоль цифру «3». Поскольку мы достигли конца функции sayThree, теперь она удаляется из стека вызовов.

|             |
|             |
|             |
|             |
|  sayOne()   |
|_____________|
   
  Call stack

Теперь выполнение программы возвращается к предыдущей функции, которой является функция sayOne. На этом этапе мы должны отметить, что выполнение этой функции возобновляется с того места, где мы ее оставили, то есть непосредственно перед строкой 3. Цикл событий запускается еще раз и обнаруживает, что стек не пуст. Он видит, что самая верхняя функция в стеке совпадает с выполняемой в данный момент функцией sayOne и, следовательно, возобновляет работу. Вызывается строка 3, которая выводит на консоль «1». Мы достигли конца функции sayOne, и она немедленно удаляется из стека вызовов.

|             |
|             |
|             |
|             |
|             |
|_____________|
   
  Call stack

Затем выполнение программы возвращается к тому месту, где было остановлено предыдущей функции (в данном случае это глобальный контекст). Итак, программа теперь возвращается к тому моменту, который находился непосредственно перед строкой 16. Теперь цикл обработки событий запускается снова, и он обнаруживает, что стек вызовов пуст. Итак, он переходит к выполнению строки 16, которая выводит на консоль цифру «5».

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

|             |
|             |
|             |
|             |
|  sayTwo()   |
|_____________|
   
  Call stack

Цикл событий проверяет стек вызовов и запускает функцию sayTwo. На консоли будет напечатано «2». Затем функция sayTwo удаляется из стека вызовов.

|             |
|             |
|             |
|             |
|             |
|_____________|
   
  Call stack

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

До сих пор в этой серии мы обсуждали только выполнение синхронного кода в Javascript. Javascript предоставляет нам асинхронные функции, такие как функция setTimeout, которая используется для задержки выполнения фрагмента кода. Мы увидим, как это вписывается в поток выполнения, в Части 3 этой серии.