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

Давайте сегодня разберемся в причинах.

Цикл событий для браузеров

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

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

Каждый раз при получении задачи создается новый стек вызовов.

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

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

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

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

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

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

Общая задача называется MacroTask, а задача с высоким приоритетом называется MicroTask.

Макрозадача включает: setTimeout, setInterval, requestAnimationFrame, Ajax, выборку, код тега скрипта.

MicroTask включает: Promise.then, MutationObserver, Object.observe.

Как понять разделение макро-микрозадач?

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

MutationObserver и Object.observe прослушивают изменение объекта, изменение - вещь очень мгновенная, мы должны реагировать немедленно, иначе оно может снова измениться, Promise - это организация асинхронного процесса, асинхронный end call то тоже очень качественно.

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

Но позже среда выполнения JS — это не только браузер, но и Node.js, он тоже для решения этих проблем, но более подробно разработан из цикла событий.

Цикл событий для Node.js

Node.js — это новая среда выполнения JS, которая также должна поддерживать асинхронную логику, включая таймеры, ввод-вывод, сетевые запросы и, разумеется, цикл обработки событий.

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

Что в этом грубого?

Цикл событий браузера разделен только на два уровня приоритета: один для макрозадач и один для микрозадач. Но между макрозадачами и между микрозадачами нет дальнейшей приоритизации.

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

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

Объяснить пять макрозадач.

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

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

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

Проверить обратный вызов:обратный вызов для выполнения setImmediate, характеризующийся обратным вызовом сразу после выполнения ввода-вывода.

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

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

Особо следует отметить еще одно отличие.

Вместо выполнения одной макрозадачи за раз, а затем выполнения всех микрозадач, цикл событий Node.js выполняет определенное количество макрозадач таймеров, затем выполняет все микрозадачи, затем выполняет определенное количество Ожидающие макро-задачи, затем переходит к выполнению всех микро-задач и оставшихся макро-задач «Опрос», «Проверка», «Закрытие».

Почему это так?

На самом деле, это легко понять по приоритету.

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

Макрозадачи Node.js также имеют приоритет, поэтому цикл событий Node.js запускает все макрозадачи с текущим приоритетом по одной, прежде чем запускать микрозадачи, а затем макрозадачу со следующим приоритетом.

То есть определенное количество макрозадач Timers, затем все микрозадачи, затем определенное количество макрозадач Pending Callback, затем все микрозадачи.

Почему вы говорите определенное число?

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

В дополнение к макрозадачам с приоритетом, микрозадачи также делятся на приоритеты, с еще одной микрозадачей высокого приоритета process.nextTick, которая выполняется перед всеми обычными микрозадачами.

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

  • Фаза таймеров:выполнение определенного количества таймеров, т. е. setTimeout, — обратные вызовы setInterval, слишком много для следующего запуска
  • Микрозадачи:выполните все микрозадачи nextTick, а затем выполните другие распространенные микрозадачи.
  • Фаза ожидания:выполнить определенное количество обратных вызовов ввода-вывода и сетевых исключений, сохранить их для следующего раза, если слишком много
    Микрозадачи: выполнить все микрозадачи nextTick, а затем выполнить другие обычные микрозадачи
  • Этап простоя/подготовки:этап для внутреннего использования
    Микрозадача: выполнение всех микрозадач nextTick, а затем выполнение других обычных микрозадач
  • Фаза опроса: выполнить определенное количество обратных вызовов данных для файлов, обратных вызовов подключения для сети и сохранить их для следующего раза, если их слишком много. Если нет обратных вызовов ввода-вывода и таймеров, проверьте фазовые обратные вызовы для обработки, заблокируйте здесь и дождитесь событий ввода-вывода.
  • Микрозадачи. Выполнение всех микрозадач nextTick, затем выполнение других обычных микрозадач.
  • Фаза проверки: выполнить определенное количество обратных вызовов setImmediate, сохранить их для следующего выполнения, если их слишком много.
  • Микрозадача:выполнить все микрозадачи nextTick, а затем выполнить другие обычные микрозадачи.
  • Фаза закрытия: выполнить определенное количество обратных вызовов события закрытия, сохранить их для следующего выполнения, если их слишком много.
    Микрозадача: выполнить все микрозадачи nextTick, а затем выполнить другие обычные микрозадачи.

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

Node.js отдает приоритет макрозадачам, от высокого к низкому, таймерам, ожиданиям, опросу, проверке, закрытию и микрозадачам, то есть микрозадачам nextTick и другим микрозадачам.

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

Это непрерывный цикл. Существует также фаза Idle/Prepare для внутренней логики Node.js, так что не беспокойтесь об этом.

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

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

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

Краткое содержание

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

Node.js также является средой выполнения JS и также использует Event Loop для поддержки асинхронности, но среда на стороне сервера является более сложной и требует более высокой производительности, поэтому Node.js имеет более точную расстановку приоритетов макросов и микрозадач.

Node.js имеет 5 типов макрозадач, а именно Таймеры, Ожидание, Опрос, Проверка, Закрытие и 2 типа микрозадач, а именно process.nextTick и другие микрозадачи.

Процесс цикла событий в Node.js выполняет определенное количество макрозадач в текущей фазе (остальные выполняются в следующем цикле), а затем выполняет все микрозадачи в 6 фазах: таймеры, ожидание, бездействие/подготовка, опрос, проверка. , Закрывать. (Редакция: так было до ноды 11, но после ноды 11 все микрозадачи выполняются для каждой макрозадачи)

Фаза Idle/Prepare используется внутри Node.js, так что не беспокойтесь об этом.

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

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

Создавайте компонуемые веб-приложения

Не создавайте веб-монолиты. Используйте Bit для создания и компоновки несвязанных программных компонентов — в ваших любимых фреймворках, таких как React или Node. Создавайте масштабируемые и модульные приложения с мощными и приятными возможностями разработки.

Перенесите свою команду в Bit Cloud, чтобы совместно размещать и совместно работать над компонентами, а также значительно ускорить, масштабировать и стандартизировать разработку в команде. Начните с компонуемых интерфейсов, таких как Design System или Micro Frontends, или исследуйте компонуемый сервер. Попробуйте →

Узнать больше