Цикл событий - один из наиболее важных аспектов JavaScript.
Производительность веб-страницы можно значительно улучшить, если мы поймем, что происходит под капотом механизма цикла событий.
Этот пост призван объяснить механизм цикла событий, как он может блокировать операции ввода-вывода и пользовательские события, пока мы выполняем любые сложные операции в javascript.
Давайте начнем с истины: «Javascript имеет однопоточный механизм».
Это означает, что по замыслу движки JavaScript - изначально браузеры - имеют один основной поток выполнения, и, проще говоря, процесс или функция B не могут выполняться до тех пор, пока процесс или функция A готово. Пользовательский интерфейс веб-страницы не реагирует на любую другую обработку JavaScript, пока он занят выполнением чего-либо - это называется блокировкой DOM.
Что такое Event Loop?
Концепция цикла событий проста. Существует бесконечный цикл - ›Механизм Javascript (движок chrome v8, Mozilla SpiderMonkey) ждет задач, выполняет их, засыпает, пока не появится другая задача.
Например:
- Каждый раз при запуске внешнего сценария ‹загружается сценарий src =” ”›, и задача состоит в том, чтобы его выполнить.
- Всякий раз, когда пользователь щелкает мышью, задача состоит в том, чтобы отправить и выполнить событие mouseclick.
- Всякий раз, когда мы используем setTimeOut, setInterval в нашей задаче сценария запускает обратные вызовы.
- и это продолжается ...
Во время этого процесса возникает ситуация, когда движок Javascript занят выполнением одной задачи, а другая задача приходит для выполнения, образует очередь задач или, в общем, мы называем это Очередью событий.
Все хорошо, тогда в чем проблема?
- Отрисовка веб-страницы никогда не происходит, пока движок javascript выполняет задачу, даже если это занимает много времени. Изменения в DOM можно увидеть только после того, как движок javascript выполнит свою задачу.
- Возможно, вы видели всплывающее окно «Страница не отвечает» в своем браузере и просили завершить задачу, это происходит, если выполнение определенной задачи занимает слишком много времени, обработка пользовательских событий и т. Д. Из-за сложных вычисления или бесконечные циклы, выполняемые в одном потоке, доступном для 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.
Теперь у нас есть два файла
- 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, веб-воркеров, которые позволяют поддерживать веб-производительность в приоритетном порядке при создании приложений. Вышеупомянутое понимание может улучшить веб-производительность и взаимодействие с пользователем, хотя мы использовали простые примеры, чтобы продемонстрировать неблокирующий ввод-вывод, мы можем гораздо лучше распространить эти концепции на наши реальные приложения. Ура! если вы узнаете что-то новое сегодня.
Удачного кодирования! 💻…