Общий обзор того, что происходит во время выполнения.
JavaScript — это однопоточный язык. Это означает, что в данном экземпляре будет только одна строка в JavaScript, выполняемая движком JavaScript.
Чтобы воспользоваться преимуществами нескольких потоков и сделать систему более производительной и отзывчивой в случае с браузерами, мы можем перенести трудоемкие операции на отдельные потоки, отличные от того, в котором выполняется наш файл JavaScript. Эти события происходят одновременно и называются асинхронными.
Параллелизм — это когда вещи работают вместе одновременно. Принимая во внимание, что параллелизм — это запуск вещей в одном и том же временном интервале.
Анатомия
Есть несколько объектов, которые выполняются во время выполнения, а именно: стек вызовов, куча, очередь задач, очередь микрозадач и самое главное, цикл событий.
При выполнении вашего JavaScript у нас есть 2 области памяти, стек вызовов и куча.
Стек вызовов
Это структура данных, которая используется для выполнения функций в нашем коде. Всякий раз, когда функция должна быть вызвана, она помещается в стек, и после завершения выполнения она извлекается из стека.
При вызове функции в стек вызовов добавляется кадр с копией локальной переменной этой функции. Если внутри этой функции вызывается другая функция, в стек вызовов добавляется еще один кадр. В противном случае, если функция возвращает значение, кадр удаляется из стека.
Рассмотрим следующий пример кода.
function eat() { var cutlery = "🥄🥄🥄"; // Local variable return makeFood("🍞"); // Push } function makeFood(food) { return food; // Pop } eat();
Для этого воспользуйтесь отладчиком браузера для проверки стека вызовов:
Вы также можете просмотреть этот стек, когда получите сообщение об ошибке StackTrace
. Кроме того, проблема StackOverflow
связана с тем, что вы превысили доступное пространство в этом стеке.
Если у вас есть асинхронный код, то он перемещается в очередь микрозадач, если это обещание, иначе он перемещается в очередь задач, если и после завершения его выполнения он вызывается для вывода.
куча
В отличие от стека вызовов, который представляет собой высокопроизводительную непрерывную область памяти, используемую для выполнения ваших функций, куча представляет собой неструктурированный пул памяти, в котором хранятся объекты и примитивные значения внутри замыкания. Это сбор мусора.
Зная, что такое стек вызовов, куча и очередь задач, мы можем понять цикл обработки событий.
Цикл событий
Длительные задачи обрабатываются циклом обработки событий. Интуитивно это просто цикл while, который ожидает сообщений из очереди и выполняет их синхронный код.
Когда вы нажимаете кнопку с помощью прослушивателя событий щелчка, он отправляет сообщение в очередь, а во время выполнения цикл событий затем выполняет этот обратный вызов.
while (queue.waitForMessage()) { queue.processNextMessage(); }
Это делает JavaScript неблокирующим, потому что единственное, что он делает, — это прослушивает события и обрабатывает обратный вызов для функции, поэтому он никогда не ждет возвращаемого значения функции. Единственное, чего он ждет, — это ЦП для обработки синхронного кода (для чего нужны всего микросекунды).
Цикл событий сначала проходит через весь синхронный код и выполняет его. При этом, если он сталкивается с асинхронным кодом, он помещает его в очередь микрозадач (если это обещание), в противном случае он помещает его в очередь задач, где асинхронный код будет выполняться в отдельном потоке, поэтому этот код не будет блокировать основной поток JavaScript.
После синхронного кода JavaScript будет проходить через очередь микрозадач, чтобы проверить, есть ли какие-либо выходные данные. Если задача выполнена, она извлекается из очереди микрозадач и помещается в очередь стека вызовов, откуда она вызывается для вывода. После этого JavaScript переходит в очередь задач и делает то же самое.
Очередь микрозадач имеет приоритет над очередью задач.
Рассмотрим следующий пример:
console.log("Sync 1: 🍩"); setTimeout(() => { console.log("Async 1: 🍕"); }, 0); console.log("Sync 2: 🍔"); Promise.resolve().then(() => { console.log("Async 2: 🍟"); }); console.log("Sync 3: 🍦"); Promise.reject().catch(() => { console.log("Async 3: 🥪"); }); // Output: // Sync 1: 🍩 // Sync 2: 🍔 // Sync 3: 🍦 // Async 2: 🍟 // Async 3: 🥪 // Async 1: 🍕
Для каждого раунда цикла событий:
- Запустить синхронный код
- Выполнение обратных вызовов очереди микрозадач
- Запуск обратных вызовов очереди задач