Цикл событий - один из наиболее важных аспектов JavaScript.

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

Этот пост призван объяснить механизм цикла событий, как он может блокировать операции ввода-вывода и пользовательские события, пока мы выполняем любые сложные операции в javascript.

Давайте начнем с истины: «Javascript имеет однопоточный механизм».

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

Что такое Event Loop?

Концепция цикла событий проста. Существует бесконечный цикл - ›Механизм Javascript (движок chrome v8, Mozilla SpiderMonkey) ждет задач, выполняет их, засыпает, пока не появится другая задача.

Например:

  • Каждый раз при запуске внешнего сценария ‹загружается сценарий src =” ”›, и задача состоит в том, чтобы его выполнить.
  • Всякий раз, когда пользователь щелкает мышью, задача состоит в том, чтобы отправить и выполнить событие mouseclick.
  • Всякий раз, когда мы используем setTimeOut, setInterval в нашей задаче сценария запускает обратные вызовы.
  • и это продолжается ...

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

Все хорошо, тогда в чем проблема?

  1. Отрисовка веб-страницы никогда не происходит, пока движок javascript выполняет задачу, даже если это занимает много времени. Изменения в DOM можно увидеть только после того, как движок javascript выполнит свою задачу.
  2. Возможно, вы видели всплывающее окно «Страница не отвечает» в своем браузере и просили завершить задачу, это происходит, если выполнение определенной задачи занимает слишком много времени, обработка пользовательских событий и т. Д. Из-за сложных вычисления или бесконечные циклы, выполняемые в одном потоке, доступном для javascript.

Чтобы лучше понять это, запустите следующие коды

возьмем функцию, которая считает от 1 до 1000000000.

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

Live Demo: здесь

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

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

Live Demo: здесь

Чтобы понять приведенную выше концепцию о том, как использование setTimeout визуализирует DOM без какой-либо задержки или без блокировки пользовательских событий:

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

Задачи планируются таким образом, чтобы браузер мог перейти из своих внутренних компонентов в область JavaScript / DOM и гарантировать, что эти действия выполняются последовательно. Между задачами браузер может отображать обновления. Переход от щелчка мыши к обратному вызову события требует планирования задачи, как и анализ HTML, а в приведенном выше примере setTimeout.

В приведенном выше коде при выполнении первого фрагмента (от 1 до 1000000) рендеринг DOM приостанавливается. После вызова setTimeout для запуска следующей задачи фрагмента он сначала ищет ожидающие события, такие как user mouseclick, mousemove, scroll или рендеринг dom, и завершает эти задания. и возвращается к запланированной функции обратного вызова setTimeout, не мешая взаимодействию пользователя. Таким образом, мы можем избежать каких-либо механизмов блокировки в пользовательском интерфейсе.

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

Пора заняться микрозадачами:

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

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

Давай попробуем что-нибудь с обещаниями (es6)

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

Как только обещание выполняется или оно уже выполнено, оно ставит в очередь микрозадачу для своих реакционных обратных вызовов. Это гарантирует, что обратные вызовы обещаний будут асинхронными, даже если обещание уже выполнено. Таким образом, вызов .then(yey, nay) против установленного обещания немедленно ставит в очередь микрозадачу. Вот почему promise1 и promise2 регистрируются после script end, так как текущий выполняющийся скрипт должен завершиться до того, как будут обработаны микрозадачи. promise1 и promise2 регистрируются до setTimeout, поскольку микрозадачи всегда выполняются перед следующей задачей.

Каков приоритет выполнения события?

Для лучшего понимания модели обработки цикла событий выполните следующие действия.

Https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model.

А вот и веб-работники:

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

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

Теперь у нас есть два файла

  1. webworker.html - ›которые создают воркер для выполнения дорогостоящей операции в рабочем потоке вместо выполнения в основном потоке.

2.worker.js - ›который содержит дорогостоящую задачу по работе ЦП и взаимодействует с файлом worker.html после завершения задачи.

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

Чтобы процитировать ссылку разработчика Mozilla

Обратите внимание, что onmessage и postMessage() необходимо подвесить на объекте Worker при использовании в основном потоке сценария, но не при использовании в рабочем потоке. Это потому, что внутри воркера он фактически является глобальной областью видимости. поэтому мы просто завершаем работу воркера после завершения задачи.

Примечание:

  • Скрипт должен обслуживаться с того же хоста или домена по соображениям безопасности, и это также причина того, что веб-воркеры не будут работать, если мы откроем файл локально по схеме file://. Я использую http-сервер npm для запуска файлов на легком http-сервере на моем локальном компьютере.
  • Веб-воркеры могут обмениваться сообщениями с основным процессом, но у них есть свои собственные переменные и собственный цикл обработки событий.
  • Веб-воркеры не имеют доступа к DOM, поэтому они полезны, в основном, для вычислений, чтобы одновременно использовать несколько ядер ЦП.

Вывод

Мы начали с понимания цикла событий и его архитектуры. Мы попробовали запустить сценарий блокировки DOM, а затем преобразовали этот сценарий в неблокирующий сценарий DOM, разделив выполнение на фрагменты с помощью setTimeout (Macrotask).

Затем мы рассмотрели обещания (микрозадачи) и их выполнение до макрозадач и публикации в рендеринге DOM.

Наконец, мы попробовали веб-работников с тем же сценарием блокировки DOM и наблюдали концепцию параллельной потоковой передачи в javascript с использованием рабочих потоков.

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

Удачного кодирования! 💻…