Когда вы изучаете JavaScript, вы часто слышите, как люди используют такие слова, как «однопоточный», «блокирующий» и «асинхронный обратный вызов». Если вообще вы похожи на меня, в чем вы киваете и соглашаетесь, как будто все так очевидно, хотя вы едва ли знаете, как эти термины действительно работают; и все же такие любопытные существа, как я, пытаются выяснить, как на самом деле работает JavaScript, тогда давайте углубимся!

Что такое JavaScript?

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

Ах, однопоточный!!

Что означает:

Один поток == Один стек вызовов == Одна вещь за раз

Давайте визуализируем, как стек вызовов выполняет операторы программы по одному ниже:

См. рис. 1. Мы видим, что есть несколько определений функций, и, наконец, движок переходит к вызову функции приветствия().

  1. Мы вызываем функцию приветствия ()
  2. Он помещаетсяв стек вызовов.
  3. Распечатывает «Привет»
  4. Метод приветствия() отключается
  5. Снова движок сталкивается с новым вызовом, т.е. функцией приветствияWorld().
  6. Он помещаетсяв стек вызовов.
  7. Распечатывает «Hello World»
  8. Метод welcomeWorld() отключается

Вам может быть интересно, что такое стек вызовов на приведенной выше диаграмме.

Это структура данных, которая отслеживает, где в программе мы находимся. Если мы входим в функцию, то эта функция помещается в стек, а если мы возвращаемся из функции, то выталкиваемся из вершины стека.

Хотите узнать, где найти этот стек вызовов?

Он находится в подсистеме JavaScript, такой как подсистема Chrome V8. См. рис. 2 ниже:

Теперь мы увидели, насколько JavaScript является однопоточным, и вы могли подумать «ДА». JS работает со стеком вызовов на месте. Но что, если нашему коду требуется некоторое время для выполнения?

Как именно мы называем код, который требует времени/медленный?

Ааа блокировка!!

Что означает:

Вещи, которые выполняются медленно или требуют много времени, относятся к коду блокировки.

Постановка задачи. Представьте, что мы хотим подписаться на канал на YouTube. Что делать, если подписка на канал заняла более x времени?

Возникла проблема: Браузеры !!

См. рис. 3. Проблема в приведенном выше сценарии заключается в том, что после того, как мы нажимаем кнопку подписки, весь экран/браузер зависает до тех пор, пока запрос на подписку не будет выполнен.

Какое решение?

Асинхронные обратные вызовы.

А, асинхронные обратные вызовы!!

Что означает:

Мы запускаем некоторый код и даем ему обратный вызов, который будет выполнен позже. Давайте визуализируем это, используя стек вызовов ниже:

См. рис. 4. При выполнении приведенного выше фрагмента кода мы видим, что:

  1. 1ˢᵗ оператор console.log() будет помещен в стек вызовов
  2. Распечатывает 1
  3. Файл console.log(1) отключается
  4. Точно так же выполняется оператор 2ⁿᵈ console.log()
  5. Затем вызов функции setTimeout помещается в стек вызовов для вывода числа 3, которое будет выполнено в течение минимум 2 секунд. Отсюда каким-то образом исчезает функция setTimeout!!
  6. Затем console.log(4) будет помещен в стек вызовов.
  7. Распечатывает 4
  8. console.log(4) отключается
  9. Через несколько секунд волшебным образом console.log(3) появляется в стеке вызовов и выводит 3

Как это произошло?

Секрет асинхронной природы JavaScript: Цикл событий !!

Сейчас вы можете подумать, что JavaScript — это однопоточный язык программирования, но это не совсем так!!

Таким образом, среда выполнения JavaScript (движок Chrome V8) выполняет одно действие за раз. Он не может сделать сетевой запрос/не может установить setTimeout, пока выполняется другой код.

Итак, как мы можем делать сетевые запросы или как мы можем выполнять setTimeout, пока выполняется какой-то другой код?

Потому что сам браузер имеет гораздо больше вещей, чем просто среда выполнения JavaScript (движок Chrome V8). Давайте посмотрим на диаграмму ниже, чтобы узнать, что это за другие вещи.

Давайте еще раз взглянем на выполнение примера асинхронного обратного вызова, чтобы понять, что представляют собой другие компоненты: Web/C++ API, очередь задач, цикл событий

См. рис. 5. При выполнении приведенного выше фрагмента кода мы видим, что:

  1. Оператор 1ˢᵗ console.log(1) помещается в стек вызовов.
  2. Распечатывает 1
  3. Файл console.log(1) отключается
  4. Точно так же выполняется оператор 2ⁿᵈ console.log()
  5. Теперь, когда setTimeout помещается в стек, он выполняется вне стека вызовов т.е. он выполняется веб-API браузера. Итак, браузер запускает таймер для нас, и теперь он будет обрабатывать его выполнение. Поскольку выполнение setTimeout еще не завершено, мы извлекаем его из стека.
  6. Мы продолжаем выполнять фрагмент для console.log(4) так же, как для console.log(1) и console.log(2)
  7. Как только веб-API завершает выполнение оператора setTimeout, он помещает результат в очередь задач. Наконец, мы добираемся до «цикла событий». Цикл событий выглядит примерно так: «Привет, наконец-то у меня есть задача. Позвольте мне взглянуть на стек, если он пуст. О да, это так! Следовательно, позвольте мне переместить 1ˢᵗ из очереди задач в стек вызовов, то есть обратный вызов».
  8. Затем результат обратного вызова выводится на консоль, то есть число 3, и выталкивается из стека вызовов.

Заключение

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

Надеюсь, после этого вы немного освоитесь с концепцией цикла событий.

На эту статью сильно повлиял доклад Филиппа Робертса о JS Event Loop. Спасибо Филипу Робертсу за видео, оно помогло мне лучше понять JavaScript.