Что такое event loop javascript

Как устроен Event Loop в JavaScript: параллельная модель и цикл событий

В Event Loop в языке JavaScript заключается секрет асинхронного программирования. Сам по себе JS является однопоточным, но при использовании нескольких умных структур данных можно создать иллюзию многопоточности (параллельная модель). Как это происходит, расскажем в этой статье.

Код JavaScript работает только в однопоточном режиме. Это означает, что в один и тот же момент может происходить только одно событие. С одной стороны это хорошо, так как такое ограничение значительно упрощает процесс программирования, здесь не возникает проблем параллелизма. Но, как правило, в большинстве браузеров в каждой из вкладок существует свой цикл событий. Среда управляет несколькими параллельными циклами.

Общим знаменателем для всех сред является встроенный механизм, называемый Event Loop JavaScript, который обрабатывает выполнение нескольких фрагментов программы, вызывая каждый раз движок JS.

Какова идея цикла событий?

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

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

Визуально процесс можно изобразить так:

event loop js

  • Stack (Стек). Представляет собой поток выполнения кода JavaScript. Event Loop выполняет одну простую задачу — осуществляет контроль стека вызовов и очереди обратных вызовов. Если стек вызовов пуст, цикл событий возьмет первое событие из очереди и отправит его в стек вызовов, который его запустит. При вызове нового метода вверху стека выделяется отдельный блок памяти. Стек вызовов отвечает за отслеживание всех операций в очереди, которые должны быть выполнены. При завершении очереди она извлекается из стека.

stack eventl oop js

  • Heap (Куча) . В куче происходит создание нового объекта.
  • Queue (Очередь) . Очередь событий отвечает за отправку новых функций на трек обработки. Он следует структуре данных очереди, чтобы поддерживать правильную последовательность, в которой все операции должны отправляться на выполнение. Если проще, то это и есть список задач, которые должны отправиться на обработку и ждут своего часа.
  • Web API. Не являются частью JavaScript, они скорее созданы на основе JS. Каждый раз, когда вызывается асинхронная функция, она отправляется в API браузера. На основе команды, полученной из стека вызовов, API запускает собственную однопоточную операцию.

Отслеживание новых событий в цикле:

Если в очереди нет задач, queue.waitForMessage ожидает их поступления.

Как события добавляются в очередь

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

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

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

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

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

Пример использования setTimeout(() => ), 0) заключается в том, чтобы вызвать функцию, но выполнить ее после выполнения всех остальных функций в коде.

bar, baz, foo — случайные имена.

При запуске кода сначала вызывается foo(). Внутри foo() мы сначала вызываем setTimeout, передавая bar в качестве аргумента, и инструктируем его таким образом, чтобы он запускался как можно быстрее, передавая 0 в качестве таймера. Затем мы вызываем baz().

Порядок функций в программе:

event loop js 2

Event loop. Источник: The JavaScript Event Loop

Почему так происходит?

Очередь событий

При вызове setTimeout(), браузер или Node.js запускают таймер. По истечении таймера (в нашем случае мы установили 0) в качестве тайм-аута, функция обратного вызова помещается в очередь событий.

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

Event Loop отдает приоритет стеку вызовов. Сначала он обрабатывает все, что находит в стеке вызовов, а когда там ничего не остается, переходит к обработке очереди событий.

setTimeout с аргументом 0 не гарантирует, что обработка будет выполнена мгновенно. Все зависит от того, сколько задач в данный момент находится в очереди. В примере ниже ”message” будет выведена быстрее обработчика callback_1. Объясняется это тем, что задержка представляет собой минимальное время, необходимое среде на выполнение запроса.

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

В заключение

Веб-сайты стали более интерактивными и динамичными, необходимость выполнения интенсивных операций стала все более актуальной (к примеру, выполнение внешних сетевых запросов для получения данных API). Чтобы обрабатывать эти операции, необходимо использование методов асинхронного программирования. Встроенный механизм Event Loop помогает JavaScript обрабатывать асинхронный код.

Как писать эффективный код на JavaScript с помощью Event Loop

Event Loop (цикл событий) — один из важнейших аспектов в JavaScript, знание которого позволяет писать более эффективный код. В статье мы рассмотрим, как работает основной поток в JavaScript и как он обрабатывает асинхронные функции.

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Долгое время я писал код на JavaScript, не до конца понимая, как он работает под капотом. В принципе, для того чтобы кодить на JavaScript, знать принципы его работы изнутри и не нужно, но это сделает ваш код лучше и позволит взглянуть на некоторые вещи в языке под другим углом.

Весь код в JavaScript выполняется в одном потоке, т. е. за один раз может обрабатываться только что-то одно. С одной стороны, это полезное ограничение, так как оно позволяет нам не задумываться об особенностях работы с параллелизмом. С другой стороны, мы постоянно должны контролировать код и заботиться о том, чтобы синхронные операции (бесконечные циклы, запрос данных по сети) не блокировали наш поток.

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

Cube Dev , Удалённо , От 8000 $

По факту окружение может одновременно управлять большим количеством «циклов событий» для обработки API-запросов. WebWorkers также имеют свой цикл событий.

JavaScript-разработчик должен знать, что его код всегда выполняется в одном цикле событий, и следить за тем, чтобы не заблокировать его.

Блокирование Event Loop

Любой код, который будет долго выполняться прежде чем вернёт управление в основной поток, блокирует выполнение любого JavaScript-кода на странице. При этом также блокируется пользовательский интерфейс, и пользователь не может с ним взаимодействовать (кликать, прокручивать страницу и т. д.).

Почти все операции ввода/вывода в JavaScript являются неблокирующими — сетевые запросы, операции с файловой системой в Node.js и т. д. Исключением являются блокирующие операции, и именно поэтому в JavaScript так популярны обратные вызовы (callbacks), а в последнее время всё чаще начинают использовать Promise и async/await.

Стек вызовов

Стек вызовов — это очередь LIFO (Last In, First Out).

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

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

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Примеры работы с Event Loop

На небольшом примере мы рассмотрим, как работает Event Loop:

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Имена функций bar, baz, foo взяты в качестве примера.

После выполнения этот код выведет:

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

В принципе, как и ожидалось.

Давайте подробно разберём, как этот код обрабатывается через Event Loop. Когда код выполняется, первым вызывается foo() , внутри foo() первой вызывается bar() , а затем baz() .

В этот момент стек вызовов выглядит так:

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Цикл обработки событий на каждой итерации проверяет, есть ли в стеке вызовы, и если да, выполняет их:

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Этот процесс продолжается до тех пор, пока стек не станет пустым.

Порядок выполнения функций

В примере выше нет ничего специфичного: JavaScript анализирует код и определяет порядок вызова функций.

Давайте посмотрим, как мы можем изменить порядок вызова функций, сделав так, что определённая функция будет вызвана последней. Для этого мы выполним вызов нашей функции посредством browser API:

Рассмотрим следующий пример:

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Результат выполнения этого кода для некоторых может стать неожиданным:

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Когда этот код выполняется, сначала вызывается foo() . Внутри foo() мы сначала вызываем setTimeout , передавая bar в качестве аргумента, а временным интервалом указываем 0, чтобы вызов произошёл настолько быстро, насколько это возможно. Затем мы вызываем baz() .

На этом этапе стек вызовов выглядит следующим образом:

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Порядок вызова функций в этом случае будет выглядеть так:

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Далее мы рассмотрим, почему так происходит.

Очередь сообщений (The Message Queue)

Когда вызывается setTimeout , браузер или Node.js запускают таймер. Когда время таймера истекает (в нашем случае это произойдёт немедленно, так как мы указали 0 в качестве временного интервала), наша callback-функция будет помещена в очередь сообщений.

В очередь сообщений также помещаются события, инициируемые пользователем (клик, нажатие клавиш на клавиатуре, движение мышки, сетевые запросы, такие как fetch ), а также события, генерируемые DOM, например onLoad .

В первую очередь Event Loop обрабатывает всё, что содержится в стеке вызовов, и только после этого начинает обрабатывать содержимое очереди.

Нам не нужно ждать, пока такие функции, как setTimeout , fetch или другие выполняют свою работу, поскольку они предоставляются браузером и живут в своих потоках. Например, если вы установите время ожидания setTimeout равным 2 секундам, вам не придётся ждать 2 секунды — ожидание происходит в отдельном потоке.

Очередь заданий (ES6 Job Queue)

ECMAScript 2015 представил концепцию очереди заданий, которая используется в Promises (также представлена в ES6/ES2015). Это способ выполнить результат асинхронной функции как можно скорее, а не помещать его в конец стека вызовов. Обещания, которые разрешаются до завершения текущей функции, будут выполняться сразу после текущей функции.

Это своего рода VIP-очередь, обработка которой имеет приоритет по отношению к обычной очереди.

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

Иллюстрация к статье «Как писать эффективный код на JavaScript с помощью Event Loop»

В этом большая разница между Promises (а также async/await, который построен на Promises) и привычными асинхронными функциями через setTimeout() или другие API-платформы.

Надеемся, статья поможет вам разобраться с работой Event Loop в JavaScript, включая работу с потоками, очередями событий и API браузера. Для наглядности мы рассмотрели несколько примеров и то, как их можно оптимизировать с точки зрения производительности.

Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API

Привет, Вы узнаете про event loop, Разберем основные ее виды и особенности использования. Еще будет много подробных примеров и описаний. Для того чтобы лучше понимать что такое event loop, javascript,web api, цикл событий,page lifecycle api , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

Что происходит, когда вы вводите URL-адрес в адресной строке браузера?

это можно увидить на это диаграмме

Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API

Как думаете, что произойдет, если запустить в консоли браузера этот фрагмент кода?

Если вы также, как и я, прочитали кучу статей про Event Loop, Main Thread, таски, микротаски и прочее, но затрудняетесь ответить на вопросы выше — эта статья для вас.

Итак, приступим. Код каждой HTML-страницы в браузере выполняется в Main Thread. Main Thread — это основной поток, где браузер выполняет JS, делает перерисовки, обрабатывает пользовательские действия и многое другое. По сути, это то место, где движок JS интегрирован в браузер.

Проще всего разобраться, глядя на схему:

Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API
Рисунок 1

Мы видим, что единственное место, через которое задачи могут попасть в Call Stack и выполниться — это Event Loop. Представьте, что вы оказались на его месте. И ваша работа успевать 'разгребать' задачи. Задачи могут быть двух типов:

  1. Личные — выполнение основного JavaScript-кода на сайте (далее будем считать, что он уже выполнился)
  2. Задачи от заказчиков — Render, Microtasks и Tasks

Скорее всего, личные задачи у вас будут приоритетнее. Event Loop с этим согласен 🙂 Остается упорядочить задачи от заказчика.

Конечно, первое, что приходит в голову — задать каждому заказчику приоритет, и выстроить их в очередь. Второе — определить, как именно будут обрабатываться задачи от каждого заказчика — по одной, все сразу или, может быть, пачками.

Взглянем на эту схему:

Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API
Рисунок 2

На основе этой схемы строится вся работа Event Loop.
После того как мы начали выполнять какой-либо script, в очередь Tasks ставится задача с выполнением этого скрипта. По мере выполнения этого кода, нам встречаются задачи от разных заказчиков, которые ставятся в соответствующие очереди. После того как завершается задача по выполнению скрипта (задача от Tasks), Event Loop идет к Microtasks (после задачи от Tasks Event Loop берет задачи от Microtasks). У него Event Loop берет задачи до тех пор, пока они не закончатся. Это значит, что если время их добавления равно времени их выполнения, то Event Loop будет бесконечно их разгребать.
Далее он идет к Render и выполняет задачи от него. Задачи от Render оптимизируются браузером и, если он посчитает, что в этом цикле не нужно ничего перерисовывать, то Event Loop просто пойдет дальше. Далее Event Loop снова берет задачи от Tasks и просит у него только одну, первую в очереди задачу, передает ее в CallStack и идет дальше по циклу.

Если у кого-то из заказчиков не оказалось задач, то Event Loop просто идет к следующему. И, наоборот, если у заказчика задачи занимают много времени, то остальные заказчики будут ждать своей очереди. А если задачи от какого-то заказчика оказались бесконечными, то Call Stack переполняется, и браузер начинает ругаться:

Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API
Рисунок 3

Теперь, когда мы поняли как работает Event Loop, пришло время разобраться, что будет после выполнения фрагментов кода в начале этой статьи.

Мы видим, что функция foo вызывает сама себя рекурсивно через setTimeout внутри, но при каждом вызове она создает задачу заказчика Tasks. Как мы помним, в цикле Event Loop при выполнении очереди задач от Tasks берет только 1 задачу в цикл. И далее происходит выполнение задач от Microtasks и Render. Поэтому этот фрагмент кода не заставит Event Loop страдать и вечно разгребать его задачи. Но будет подкидывать новую задачу для заказчика Tasks на каждом круге.

Давайте попробуем выполнить этот скрипт в браузере Google Chrome. Для этого я создал простой HTML-документ и подключил в нем script.js с этим фрагментом кода. После открытия документа заходим в инструменты разработчика, и открываем вкладку Perfomance и жмем там кнопку 'start profiling and reload page':

Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API
Рисунок 4

Видим, что задачи от Tasks выполняются по одной в цикл, примерно раз в 4ms.

Рассмотрим вторую задачку:

Здесь мы видим тоже самое, что и в примере выше, но вызов foo добавляет задачи от Microtasks, а они выполняются все, пока не закончатся. А это значит, что пока Event Loop не закончит их, перейти к следующему заказчику он не сможет 🙁 И мы видим снова грустную картинку.

Взглянем на это в интрументах разработчкика:

Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API
Рисунок 5

Мы видим, что микротаски выполняются примерно раз в 0.1ms, и это в 40 раз быстрее, чем очередь Tasks. Все потому, что они выполняются все и сразу. В нашем примере очередь движется бесконечно. Для визуализации я уменьшил ее до 100 000 итераций.

Концепция жизненного цикла JS

. Современные JavaScript движки внедряют/имплементируют и существенно оптимизируют этот процесс.

Визуальное представление

Для лучшего визуального представления работы Event loop,

Вызов любой функции создает контекст выполнения (Execution Context). При вызове вложенной функции создается новый контекст, а старый сохраняется в специальной структуре данных — стеке вызовов (Call Stack).

Когда вызывается функция g, создается первый контекст выполнения, содержащий аргументы функции g и локальные переменные. Когда g вызывает f, создается второй контекст с аргументами f и ее локальными переменными. И этот контекст выполнения f помещается в стек вызовов выше первого. Когда f возвращает результат, верхний элемент из стека удаляется. Когда g возвращает результат, ее контекст также удалится, и стек становится пустым.

Объекты размещаются в куче. Куча — это просто имя для обозначения большой неструктурированной области памяти.

Очередь

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

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

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

цикл событий JS

Модель событийного цикла (event loop) называется так потому, что отслеживает новые события в цикле:

queue.waitForMessage ожидает поступления задач, если очередь пуста.

Запуск до завершения

Каждая задача выполняется полностью, прежде чем начнет обрабатываться следующая. Благодаря этому мы точно знаем: когда выполняется текущая функция – она не может быть приостановлена и будет целиком завершена до начала выполнения другого кода (который может изменить данные, с которыми работает текущая функция). Это отличает JavaScript от такого языка программирования как C. Поскольку в С функция, запущенная в отдельном потоке, в любой момент может быть остановлена, чтобы выполнить какой-то другой код в другом потоке.

У данного подхода есть и минусы. Если задача занимает слишком много времени, то веб-приложение не может обрабатывать действия пользователя в это время (например, скролл или клик). Браузер старается смягчить проблему и выводит сообщение "скрипт выполняется слишком долго" ("a script is taking too long to run") и предлагает остановить его. Хорошей практикой является создание задач, которые исполняются быстро, и если возможно, разбиение одной задачи на несколько мелких.

Добавление событий в очередь

В браузерах события добавляются в очередь в любое время, если событие произошло, а так же если у него есть обработчик. В случае, если обработчика нет – событие потеряно. Так, клик по элементу, имеющему обработчик события по событию click , добавит событие в очередь, а если обработчика нет – то и событие в очередь не попадет.

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

Нулевые задержки

Нулевая задержка не дает гарантии, что обработчик выполнится через ноль миллисекунд. Вызов setTimeout с аргументом 0 (ноль) не завершится за указанное время. Выполнение зависит от количества ожидающих задач в очереди. Например, сообщение ''this is just a message'' из примера ниже будет выведено на консоль раньше, чем произойдет выполнение обработчика сb1. Это произойдет, потому что задержка – это минимальное время, которое требуется среде выполнения на обработку запроса.

Связь нескольких потоков между собой

Web Worker или кросс-доменный фрейм имеют свой собственный стек, кучу и очередь событий. Два отдельных событийных потока могут связываться друг с другом, только через отправку сообщений с помощью метода postMessage. Этот метод добавляет сообщение в очередь другого, если он конечно принимает их.

Никогда не блокируется

Очень интересное свойство цикла событий в JavaScript, что в отличие от множества других языков, поток выполнения никогда не блокируется. Обработка I/O обычно осуществляется с помощью событий и функций обратного вызова, поэтому даже когда приложение ожидает запрос от IndexedDB или ответ от XHR, оно может обрабатывать другие процессы, например пользовательский ввод.

Существуют хорошо известные исключения как alert или синхронный XHR, но считается хорошей практикой избегать их использования.

Page Lifecycle API

Жизненный цикл приложения — это ключевой способ управления ресурсами современных операционных систем. В Android, iOS и последних версиях Windows приложения могут быть запущены и остановлены в любой момент ОС. Это позволяет этим платформам оптимизировать и перераспределять ресурсы там, где они наиболее выгодны пользователю.

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

Хотя на веб-платформе уже давно есть события, связанные с состояниями жизненного цикла, такие как load , unload и visibilitychange — эти события позволяют разработчикам реагировать только на инициированные пользователем изменения состояния жизненного цикла. Чтобы Интернет мог надежно работать на маломощных устройствах (и в целом уделять больше внимания ресурсам на всех платформах), браузерам необходим способ упреждающего восстановления и перераспределения системных ресурсов.

Фактически, браузеры сегодня уже принимают активные меры по экономии ресурсов для страниц на фоновых вкладках, и многие браузеры (особенно Chrome) хотели бы делать гораздо больше — чтобы уменьшить общий объем ресурсов.

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

Page Lifecycle API пытается решить эту проблему:

  • Внедрение и стандартизация концепции состояний жизненного цикла в Интернете.
  • Определение новых, инициируемых системой состояний, которые позволяют браузерам ограничивать ресурсы, которые могут потребляться скрытыми или неактивными вкладками.
  • Создание новых API-интерфейсов и событий, которые позволяют веб-разработчикам реагировать на переходы в эти новые инициированные системой состояния и обратно.

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

В оставшейся части этого поста будут представлены новые функции жизненного цикла страницы, доступные в Chrome 68, и исследовано, как они связаны со всеми существующими состояниями и событиями веб-платформы. В нем также будут даны рекомендации и передовые методы работы, которую разработчики должны (и не должны) выполнять в каждом штате.

Обзор состояний и событий Lifecycle страницы

Все состояния жизненного цикла страницы являются дискретными и взаимоисключающими, то есть страница может одновременно находиться только в одном состоянии. И большинство изменений в состоянии жизненного цикла страницы обычно можно наблюдать через события DOM (см. Рекомендации разработчика для каждого состояния для исключений).

Возможно, самый простой способ объяснить состояния жизненного цикла страницы, а также события, которые сигнализируют о переходах между ними, — это использовать схему:

Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API

состояния

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

Страница находится в активном состоянии, если она видна и имеет фокус ввода.

Возможные предыдущие состояния:
passive (через focus событие)

Возможные следующие состояния:
passive (через событие) blur

Страница находится в passive состоянии, если она видна и не имеет фокуса ввода.

Возможные предыдущие состояния:
активно (по blur событию)
hidden (по событию) visibilitychange

Возможные следующие состояния:
active (через focus событие)
hidden (через событие) visibilitychange

Страница находится в скрытом состоянии, если она не видна и не была заморожена.

Возможные предыдущие состояния:
passive (через событие) visibilitychange

Возможные следующие состояния:
passive (через событие) visibilitychange
frozen (через freeze событие)
terminated (через pagehide событие)

В frozen состоянии браузер приостанавливает выполнение замораживаемых задач на странице в очереди задач до страницы размораживается. Это означает, что такие вещи, как таймеры JavaScript и обратные вызовы выборки, не работают. Уже запущенные задачи могут завершиться (наиболее важно обратный вызов), но они могут быть ограничены в том, что они могут делать и как долго они могут работать. freeze

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

Возможные предыдущие состояния:
hidden (через freeze событие)

Страница находится в завершенном состоянии после того, как она начала выгрузку и очистку из памяти браузером . Об этом говорит сайт https://intellect.icu . В этом состоянии нельзя запускать новые задачи , а выполняемые задачи могут быть прекращены, если они выполняются слишком долго.

Возможные предыдущие состояния:
hidden (через pagehide событие)

Возможные следующие состояния:
НЕТ

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

В отключенном состоянии сама вкладка ( включая заголовок вкладки и значок ) обычно видна пользователю, даже если страница исчезла.

Возможные предыдущие состояния:
frozen (события не запущены)

Возможные следующие состояния:
НЕТ

События

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

Элемент DOM получил фокус.

Примечание: focus событие не обязательно сигнал изменения состояния. Он сигнализирует об изменении состояния только в том случае, если страница ранее не имела фокуса ввода.

Возможные предыдущие состояния:passive

Возможные текущие состояния:active

Элемент DOM потерял фокус.

Примечание: blur событие не обязательно сигнал изменения состояния. Он сигнализирует об изменении состояния только в том случае, если страница больше не имеет фокуса ввода (т.е. страница не просто переключила фокус с одного элемента на другой).

Возможные предыдущие состояния:active

Возможные текущие состояния:passive

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

Возможные предыдущие состояния:
passive

Возможные текущие состояния:
passive

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

Возможные предыдущие состояния:
hidden

Возможные текущие состояния:frozen

Браузер возобновил остановленную страницу.

Возможные предыдущие состояния:frozen

Возможные текущие состояния:
active (если за ним следует событие) pageshow
passive (если за ним следует событие) pageshow

Переход к записи истории сеанса.

Это может быть либо новая загрузка страницы, либо страница, взятая из обратного кэша . Если страница была взята из Back-Forward Cache, persisted свойство события равно true , в противном случае — false .

Возможные предыдущие состояния:
frozen ( resume событие также сработало)

Возможные текущие состояния:
active
passive

Переход к записи в истории сеанса.

Если пользователь переходит на другую страницу и браузер может добавить текущую страницу в кэш обратного продвижения, чтобы использовать его позже, persisted свойство события — true . Когда true страница переходит в frozen состояние, в противном случае она переходит в состояние завершения .

Возможные предыдущие состояния:hidden

Возможные текущие состояния:
frozen ( event.persisted верно, событие следует) freeze
terminated ( event.persisted ложно, следует событие) unload

Окно, документ и его ресурсы будут выгружены. Документ все еще виден, и на этом этапе событие все еще можно отменить.

Предупреждение: beforeunload событие должно использоваться только для оповещения пользователя о несохраненных изменениях. После сохранения этих изменений событие должно быть удалено. Его никогда не следует добавлять на страницу безоговорочно, поскольку в некоторых случаях это может снизить производительность. Подробности см. В разделе устаревших API .

Возможные предыдущие состояния:hidden

Возможные текущие состояния:terminated

Предупреждение. Использование unload события никогда не рекомендуется, поскольку оно ненадежно и в некоторых случаях может снизить производительность. Дополнительные сведения см. В разделе устаревших API .

Возможные предыдущие состояния:hidden

Возможные текущие состояния:terminated

* Указывает на новое событие, определенное API жизненного цикла страницы

Новые функции, добавленные в Chrome 68

На приведенной выше диаграмме показаны два состояния, которые инициируются системой, а не пользователем: frozen и отклонено . Как упоминалось выше, браузеры сегодня уже иногда замораживают и удаляют скрытые вкладки (по своему усмотрению), но разработчики не имеют возможности узнать, когда это происходит.

В Chrome 68 разработчики теперь могут следить за замораживанием и размораживанием скрытой вкладки, отслеживая события freeze и . resume document

В Chrome 68 document объект теперь также включает wasDiscarded свойство. Чтобы определить, была ли страница отброшена во время нахождения на скрытой вкладке, вы можете проверить значение этого свойства во время загрузки страницы (примечание: для повторного использования отброшенные страницы необходимо перезагрузить).

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

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

Наблюдение за состояниями Lifecycle страницы в коде

В активном , passive и hidden состояниях можно запустить код JavaScript, который определяет текущее состояние жизненного цикла страницы из существующих API веб-платформы.

С другой стороны, frozen и завершенное состояния могут быть обнаружены только в их соответствующем прослушивателе событий ( freeze и pagehide ) по мере изменения состояния.

Наблюдение за изменениями состояния

Основываясь на getState() функции, определенной выше, вы можете наблюдать за всеми изменениями состояния жизненного цикла страницы с помощью следующего кода.

Приведенный выше код выполняет три функции:

  • Устанавливает начальное состояние с помощью getState() функции.
  • Определяет функцию, которая принимает следующее состояние и, если есть изменение, записывает изменения состояния в консоль.
  • Добавляет прослушиватели событий для всех необходимых событий жизненного цикла, которые, в свою очередь, вызывают logStateChange() и переходят в следующее состояние.

Предупреждение! Этот код дает разные результаты в разных браузерах, поскольку порядок и надежность событий не были последовательно реализованы. Чтобы узнать, как лучше всего справиться с этими несоответствиями, см. Раздел «Управление различиями между браузерами» .

Следует отметить, что в приведенном выше коде все прослушиватели событий добавляются window и все проходят . На это есть несколько причин:

  • Не все события жизненного цикла страницы имеют одну и ту же цель. pagehide , и pageshow стреляют window ; visibilitychange , freeze и resume запускаются document , и focus и blur запускаются для соответствующих элементов DOM.
  • Большинство этих событий не всплывают, а это означает, что невозможно добавить не фиксирующие прослушиватели событий к общему элементу-предку и наблюдать за ними.
  • Фаза захвата выполняется перед целевой фазой или фазой пузырька, поэтому добавление туда слушателей помогает обеспечить их выполнение до того, как другой код сможет их отменить.

Управление различиями между браузерами

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

Более того, события, реализованные сегодня во всех браузерах, не реализованы последовательно. Например:

  • Некоторые браузеры не запускают blur событие при переключении вкладок. Это означает (вопреки диаграмме и таблицам выше) страница может перейти из активного состояния в hidden , не пройдя сначала passive.
  • Некоторые браузеры реализуют обратный кэш , а API жизненного цикла страницы классифицирует кэшированные страницы как находящиеся в frozen состоянии. Так как этот API является новым, эти браузеры еще не реализовать freeze и resume событие, хотя это состояние все еще можно наблюдать с помощью pagehide и pageshow событий.
  • В более старых версиях Internet Explorer (10 и ниже) visibilitychange событие не реализовано .
  • Порядок рассылки pagehide и visibilitychange событий изменились . Раньше браузеры отправляли сообщения visibilitychange после pagehide того, как состояние видимости страницы было видимым, когда страница выгружалась. Новые версии Chrome будут отправлены visibilitychange раньше pagehide , независимо от состояния видимости документа во время выгрузки.
  • Safari не надежно сгореть pagehide или visibilitychange события при закрытии вкладки (WebKit ошибки: 151610 и 151234 ), поэтому в Safari вам , возможно , потребуется также слушать beforeunload события для того , чтобы обнаружить изменения в hidden состоянии. Но поскольку beforeunload событие можно отменить, вам нужно подождать, пока событие не закончит распространение, чтобы узнать, изменилось ли состояние на hidden . Важно : использовать beforeunload событие таким образом следует только в Safari, так как использование этого события в других браузерах может снизить производительность. Подробности см. В разделе устаревших API .

Чтобы разработчикам было легче справляться с этими несоответствиями между браузерами и сосредоточиться исключительно на соблюдении рекомендаций и передовых методов состояния жизненного цикла , мы выпустили PageLifecycle.js , библиотеку JavaScript для наблюдения за изменениями состояния API жизненного цикла страницы.

PageLifecycle.js нормализует кроссбраузерные различия в порядке запуска событий, так что изменения состояния всегда происходят точно так, как указано в диаграмме и таблицах в этой статье (и делают это во всех браузерах).

Рекомендации разработчиков для каждого состояния

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

Например, явно не имеет смысла отображать временное уведомление для пользователя, если страница находится в hidden состоянии. Хотя этот пример довольно очевиден, есть и другие не столь очевидные рекомендации, которые стоит перечислить.

Активное состояние является наиболее критическим моментом для пользователя и , таким образом , наиболее важное время для вашей страницы будет реагировать на ввод данных пользователя .

Любая работа, не связанная с пользовательским интерфейсом, которая может блокировать основной поток, должна быть переведена на периоды простоя или передана веб- исполнителю .

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

Когда страница меняется с активной на passive, самое время сохранить несохраненное состояние приложения.

Когда страница меняется с passive на hidden , возможно, пользователь не будет с ней взаимодействовать снова, пока она не будет перезагружена.

Переход к hidden также часто последнее изменение состояния , что это надежно наблюдаемым разработчики (это особенно актуально на мобильном телефоне, так как пользователи могут закрывать вкладки или само приложение браузера, а также beforeunload , pagehide и unload события не обжигают в тех случаях).

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

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

В Frozen состоянии замораживаемые задачи в очередях задач приостанавливаются до тех пор, пока страница не разморозится, что может никогда не произойти (например, если страница будет отброшена).

Это означает, что когда страница изменяется с hidden на Frozen , важно, чтобы вы остановили любые таймеры или разорвали любые соединения, которые в случае блокировки могут повлиять на другие открытые вкладки в том же источнике или повлиять на способность браузера помещать страницу в Back- Прямой кэш .

В частности, важно, чтобы вы:

  • Закройте все открытые соединения IndexedDB .
  • Закройте открытые соединения BroadcastChannel .
  • Закройте активные соединения WebRTC .
  • Остановите любой опрос сети или закройте все открытые подключения через веб-сокеты .
  • Снимите все удерживаемые веб-блокировки .

Вы также должны сохранить любое динамическое состояние просмотра (например, положение прокрутки в бесконечном списке) в (или через IndexedDB ), которое вы хотели бы восстановить, если бы страница была отброшена и перезагружена позже. sessionStorage commit()

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

Как правило, вам не нужно предпринимать никаких действий, когда страница переходит в Terminated состояние.

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

Кроме того (как указано в рекомендациях для hidden состояния ), разработчикам очень важно понимать, что переход в завершенное состояние не может быть надежно обнаружен во многих случаях (особенно на мобильных устройствах), поэтому разработчики, которые зависят от событий завершения (например beforeunload , pagehide , и unload ), вероятно, теряют данные.

Discarded состояние не наблюдается разработчиками в то время , когда страница будет Discarded . Это связано с тем, что страницы обычно Discarded из-за ограничений ресурсов, а размораживание страницы только для того, чтобы позволить скрипту запускаться в ответ на событие сброса, в большинстве случаев просто невозможно.

В результате вы должны подготовиться к возможности отмены изменения со hidden на Frozen , а затем вы можете реагировать на восстановление отклоненной страницы во время загрузки страницы, проверяя document.wasDiscarded .

Еще раз повторюсь, поскольку надежность и упорядочение событий жизненного цикла не всегда реализованы во всех браузерах, самый простой способ следовать советам в таблице выше — использовать PageLifecycle.js .

Устаревшие API жизненного цикла, которых следует избегать

Событие разгрузки

Ключевой момент: никогда не используйте unload событие в современных браузерах.

Многие разработчики рассматривают unload событие как гарантированный обратный вызов и используют его как сигнал конца сеанса для сохранения состояния и отправки аналитических данных, но делать это крайне ненадежно , особенно на мобильных устройствах! unload Событие не срабатывает во многих типичных ситуациях разгрузочных, в том числе закрытия вкладки из вкладок переключателя на мобильном телефоне или закрытие приложения браузера из приложения переключателя.

По этой причине всегда лучше полагаться на visibilitychange событие, чтобы определить, когда сеанс заканчивается, и рассматривать hidden состояние как последнее надежное время для сохранения данных приложения и пользователя .

Более того, простое присутствие зарегистрированного unload обработчика событий (с помощью любого onunload или addEventListener() ) может помешать браузерам помещать страницы в кэш обратного направления для более быстрой загрузки вперед и назад.

Во всех современных браузерах (включая IE11) рекомендуется всегда использовать pagehide событие для обнаружения возможных выгрузок страницы (также известного как завершенное состояние), а не unload событие. Если вам нужна поддержка Internet Explorer версии 10 и ниже, вы должны определить pagehide событие и использовать его только в том unload случае, если браузер не поддерживает pagehide :

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

Оптимизация рендеринга браузера

Примечания по оптимизации рендеринга в браузере и плавности до 60 кадров в секунду!

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

Для этого браузер выполняет шесть различных задач:

  1. Скачивание и анализ HTML, CSS и JavaScript
  2. Оценка JavaScript
  3. Расчет стилей для элементов
  4. Размещение элементов на странице — Laying
  5. Рисование фактических пикселей элементов — Painting

Иногда вы можете услышать термин «растеризация», используемый в сочетании с рисованием. Это потому, что рисование на самом деле представляет собой две задачи: 1) создание списка вызовов отрисовки и 2) заполнение пикселей. Последнее называется «растеризацией», поэтому всякий раз, когда вы видите записи рисования в DevTools, вы должны думать об этом как о включении растеризации.

Ставить пользователя в центр производительности

  • 100 миллисекунд
    • Отвечайте на действие пользователя в пределах этого временного окна, и он почувствует, что результат будет немедленным. Если немного больше, то связь между действием и реакцией прерывается.
    • В этом окне все кажется частью естественного и непрерывного выполнения задач. Помимо этого, пользователь потеряет фокус на выполняемой задаче. Для большинства пользователей Интернета загрузка страницы или изменение представлений представляет собой задачу.

    Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API

    60 кадров в секунду = 16 мс / кадр, но на самом деле у вас есть только 10–12 мс на выполнение всей работы из-за накладных расходов браузера.

    Жизненные циклы приложений (RAIL)

    • отклик Response
    • Анимации Animations
    • Холостой ход Idle
    • Load Загрузка (XHR, Websockets, импорт HTML и т. Д.)

    Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API

    Актуальный хронологический порядок

    1. Загрузить (

    16 мс) На самом деле мы получаем

    Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API

    Что происходит при смене стиля?

    Например, во время изменения непрозрачности или анимации преобразования запускается только композит.

    • Страница не получает новый HTML, поэтому не нужно создавать DOM.
    • Страница не получает новый CSS, поэтому создавать CSSOM не нужно.
    • Страница не получает новый HTML или CSS, поэтому трогать дерево рендеринга не нужно.
    • Если изменение непрозрачности или трансформации влияет на элемент на его собственном слое, макет запускать не нужно.
    • Если изменение непрозрачности или трансформации влияет на элемент на его собственном слое, рисовать не нужно.

    Чтобы код JavaScript работал быстрее

    • Не зацикливайтесь на микрооптимизации, например. for-loop vs while и т. д., поскольку разные движки JS (V8 и т. д.) обрабатывают его по-разному.
    • JS может запускать каждую часть конвейера рендеринга (изменения стиля, макета, рисования и компоновки), следовательно, запускать его как можно раньше в каждом кадре.

    Анимация(Animation)

    • requestAnimationFrame — это инструмент перехода для создания анимации.
      • Планирует запуск JavaScript как можно раньше в каждом кадре.
      • Браузер может оптимизировать параллельные анимации вместе в один цикл перекомпоновки и перерисовки, что приводит к более точной анимации. Например, анимация на основе JS, синхронизированная с переходами CSS или SVG SMIL.
      • Кроме того, если вы запускаете цикл анимации на вкладке, которая не отображается, браузер не будет поддерживать ее работу, что означает меньшее использование ЦП, графического процессора и памяти, что приведет к гораздо более длительному времени автономной работы.
      • Из-за накладных расходов браузера мы получаем около 10 мс, поэтому у JS есть время около 3 мс.

      Веб-воркеры (Webworkers)

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

      Event Loop как устроен цикл событий в браузере, оптимизация рендеринга ,Page Lifecycle API

      Стили и макет (пересчет стилей) Styles and Layout (Recalc Styles)

      • Стоимость пересчета стилей линейно зависит от количества элементов на странице.
      • БЭМ : модификатор элемента блока: используйте этот стиль для селекторов CSS.
      • Сопоставление классов часто является самым быстрым селектором в современных браузерах.
      • Сокращение времени на «Пересчет стилей».
        • Уменьшить затронутые элементы (меньше изменений в дереве рендеринга)
        • Снижение сложности селектора (меньше тегов и имен классов для выбора элементов)

        Repaints and Reflows

        Рисование — это процесс, с помощью которого браузер берет свою абстрактную коллекцию элементов со всеми их свойствами и фактически вычисляет пиксели для рисования. Это включает в себя вычисление стилей, таких как тени и градиенты блоков, а также изменение размеров изображений. Перерисовка происходит, когда в оболочку элементов вносятся изменения, которые изменяют видимость, но не влияют на ее макет. Примеры этого включают контур, видимость или цвет фона. Согласно Opera, перерисовка обходится дорого, потому что браузер должен проверять видимость всех других узлов в дереве DOM.

        Перекомпоновка еще более критична для производительности, потому что она включает изменения, влияющие на макет части страницы (или всей страницы). Перекомпоновка элемента вызывает последующую перекомпоновку всех дочерних элементов и элементов-предков, а также любых элементов, следующих за ним в DOM. Согласно Opera, большинство перекомпоновок по существу вызывают перерисовку страницы.

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

        К сожалению, много чего. Среди них некоторые, которые особенно важны при написании CSS:

        • Изменение размера окна.
        • Смена шрифта.
        • Добавление или удаление таблицы стилей.
        • Изменения содержимого, например, когда пользователь вводит текст в поле ввода.
        • Активация псевдоклассов CSS, таких как: hover (в IE активация псевдокласса родственного брата)
        • Манипулирование атрибутом класса.
        • Скрипт, управляющий DOM.
        • Расчет offsetWidth и offsetHeight.
        • Установка свойства атрибута стиля

        Для полного списка проверьте суть Пола Айриша

        Compositing and Painting

        • Обновление дерева слоев: происходит, когда внутренний движок Chrome (Blink) определяет, какие слои необходимы для страницы. Он смотрит на стили элементов и определяет, в каком порядке все должно быть и сколько слоев необходимо.
        • Вы можете добавлять независимые элементы страницы в собственный слой. Однако добавление большого количества слоев требует затрат, поэтому используйте его везде, где это имеет смысл.
        • Добавление слоев с помощью CSS:
          • Обычный способ — поддерживается во всех браузерах: transform: translatez(o);
          • Новый способ — поддержка Chrome / Firefox / Нет IE-Edge: will-change: transform;

          Надеюсь, эта статья была вам полезной, и теперь вы понимаете, как работает Event Loop, и что 'творится' в примерах кода выше.

          На этом все! Теперь вы знаете все про event loop, Помните, что это теперь будет проще использовать на практике. Надеюсь, что теперь ты понял что такое event loop, javascript,web api, цикл событий,page lifecycle api и для чего все это нужно, а если не понял, или есть замечания, то нестесняся пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

          Ответы на вопросы для самопроверки пишите в комментариях, мы проверим, или же задавайте свой вопрос по данной теме.

          Объяснение Event Loop в Javascript с помощью визуализации

          Объяснение Event Loop в Javascript с помощью визуализации

          Это перевод статьи Лидии Холли, где она объясняет с помощью визуализации как работает Event Loop в JS.

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

          JavaScript это однопоточный язык: одновременно может выполняться только одна задача. Обычно в этом нет ничего сложного, но теперь представьте, что вы запускаете задачу, которая занимает 30 секунд. Да. Во время этой задачи мы ждем 30 секунд, прежде чем что-либо еще может произойти (по умолчанию JavaScript запускается в главном потоке браузера, поэтому весь пользовательский интерфейс будет ждать) Это 2020 год, никто не хочет медленный, сайт который тупит.

          К счастью, браузер предоставляет нам некоторые функции, которые сам механизм JavaScript не предоставляет: Web API. Который включает в себя DOM API, setTimeout, HTTP-запросы и так далее. Это может помочь нам создать асинхронное неблокирующее поведение .

          Когда мы вызываем функцию, она добавляется в call stack(стек вызовов). Стек вызовов является частью механизма JS, это не зависит от браузера. Это классический взгляд на стек, т.е first in, last out. Когда функция возвращает значение, она "выталкивается" из стека.

          Функция response возвращает функцию setTimeout. SetTimeout предоставляется нам через веб-API: он позволяет нам делеить задачи, не блокируя основной поток. Callback функция, которую мы передали в функцию setTimeout , лямбда функция () => добавляется в веб-API. Тем временем функция setTimeout и функция response извлекаются из стека, они оба возвращают свои значения.

          В Web API таймер работает до тех пор, пока второй аргумент, который мы передали ему, не подождет 1000 мс. Callback не сразу добавляется в стек вызовов, а передается в нечто, называемое очередью.

          Это может сбивать с толку: это не означает, что callback функия добавляется в стек вызовов (таким образом, возвращает значение) через 1000 мс! Он просто добавляется в очередь через 1000 мс. Но в этой очереди, функция должна ждать пока придет ее черёд.

          Теперь это та часть, которую мы все ждали. Время для event loop выполнить единственную задачу: соединить очередь со стеком вызовов! Если стек вызовов пуст, то есть, если все ранее вызванные функции вернули свои значения и были извлечены из стека, первый элемент в очереди добавляется в стек вызовов. В этом случае никакие другие функции не были вызваны, что означает, что стек вызовов был пуст к тому времени, когда callback функция была первым элементом в очереди.

          callback добавляется в стек вызовов, вызывается и возвращает значение, а также извлекается из стека.

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

          Давайте посмотрим, что происходит, когда мы запускаем этот код в браузере:

          1. Мы вызываем bar . bar возвращает функцию setTimeout .
          2. Callback который мы передали в setTimeout добавляется в Web API, функция setTimeout и bar извлекаются из стека вызовов.
          3. Таймер запускается, тем временем foo вызывается и записывает в журнал First . foo возвращает (undefined), baz вызыввается и callback добавляется в очередь
          4. baz логиркнт Third . Цикл обработки событий видит, что коллстек пуст после возврата baz , после чего колбэк добавляется в стек вызовов.
          5. Callback логирует Second .

          Надеюсь, что это заставит вас чувствовать себя более уверено с циклом событий (event loop)! Не беспокойтесь, если это все еще кажется запутанным, самое важное — понять, откуда могут возникнуть определенные ошибки или специфическое поведение.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *