Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

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

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

function foo() {
  setTimeout(foo, 0);
}

foo();


А этот?

function foo() {
  Promise.resolve().then(foo);
}

foo();


Если вы также, как и я, прочитали кучу статей про 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, пришло время разобраться, что будет после выполнения фрагментов кода в начале этой статьи.

function foo() {
  setTimeout(foo, 0);
}

foo();


Мы видим, что функция 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.

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

function foo() {
  Promise.resolve().then(foo);
}

foo();


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

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

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

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

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

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

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

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

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

Стек

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

function f(b) {
  var a = 12;
  return a + b + 35;
}

function g(x) {
  var m = 4;
  return f(m * x);
}

g(21);

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

Куча

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

Очередь

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

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

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

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

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

while(queue.waitForMessage()){
  queue.processNextMessage();
}

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

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

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

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

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

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

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

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

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

(function () {

  console.log('this is the start');

  setTimeout(function cb() {
    console.log('this is a msg from call back');
  });

  console.log('this is just a message');

  setTimeout(function cb1() {
    console.log('this is a msg from call back1');
  }, 0);

  console.log('this is the end');

})();

// "this is the start"
// "this is just a message"
// "this is the end"
// "this is a msg from call back"
// "this is a msg from call back1"

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

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

состояния

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

State Описание
Active

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

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

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

Passive

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

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

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

Hidden

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

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

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

Frozen

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

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

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

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

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

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

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

Discarded

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

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

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

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

События

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

имя Детали
focus

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

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

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

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

blur

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

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

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

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

visibilitychange

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

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

hidden

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

hidden

freeze *

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

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

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

resume *

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

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

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

hidden

pageshow

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

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

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

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

hidden

pagehide

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

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

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

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

beforeunload

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

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

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

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

unload

Страница выгружается.

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

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

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

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

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

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

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

document.addEventListener('freeze', (event) => {
  // The page is now frozen.
});

document.addEventListener('resume', (event) => {
  // The page has been unfrozen.
});

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

if (document.wasDiscarded) {
  // Page was previously discarded by the browser while in a hidden tab.
}

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

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

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

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

const getState = () => {
  if (document.visibilityState === 'hidden') {
    return 'hidden';
  }
  if (document.hasFocus()) {
    return 'active';
  }
  return 'passive';
};

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

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

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

// Stores the initial state using the `getState()` function (defined above).
let state = getState();

// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the `state` value defined above.
const logStateChange = (nextState) => {
  const prevState = state;
  if (nextState !== prevState) {
    console.log(`State change: ${prevState} >>> ${nextState}`);
    state = nextState;
  }
};

// These lifecycle events can all use the same listener to observe state
// changes (they call the `getState()` function to determine the next state).
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
  window.addEventListener(type, () => logStateChange(getState()), {capture: true});
});

// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener('freeze', () => {
  // In the freeze event, the next state is always frozen.
  logStateChange('frozen');
}, {capture: true});

window.addEventListener('pagehide', (event) => {
  if (event.persisted) {
    // If the event's persisted property is `true` the page is about
    // to enter the Back-Forward Cache, which is also in the frozen state.
    logStateChange('frozen');
  } else {
    // If the event's persisted property is not `true` the page is
    // about to be unloaded.
    logStateChange('terminated');
  }
}, {capture: true});

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

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

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

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

  • Не все события жизненного цикла страницы имеют одну и ту же цель. 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 состоянии. Хотя этот пример довольно очевиден, есть и другие не столь очевидные рекомендации, которые стоит перечислить.

State Рекомендации разработчиков
Active

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

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

Passive

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

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

Hidden

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

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

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

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

Frozen

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

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

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

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

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

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

Terminated

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

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

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

Discarded

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:

const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';

addEventListener(terminationEvent, (event) => {
  // Note: if the browser is able to cache the page, `event.persisted`
  // is `true`, and the state is frozen rather than terminated.
}, {capture: true});

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

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

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

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

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

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

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

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

  • 100 миллисекунд
    • Отвечайте на действие пользователя в пределах этого временного окна, и он почувствует, что результат будет немедленным. Если немного больше, то связь между действием и реакцией прерывается.
  • 1 секунда
    • В этом окне все кажется частью естественного и непрерывного выполнения задач. Помимо этого, пользователь потеряет фокус на выполняемой задаче. Для большинства пользователей Интернета загрузка страницы или изменение представлений представляет собой задачу.
  • 16 миллисекунд
    • Учитывая, что экран обновляется 60 раз в секунду, это окно представляет время, за которое на экран выводится один кадр (профессор Математик говорит, что 1000 ÷ 60 = ~ 16). Люди исключительно хороши в отслеживании движения, и им не нравится, когда их ожидания движения не выполняются - либо из-за переменной частоты кадров, либо из-за периодических остановок.

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

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

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

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

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

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

  1. Загрузить (~ 1 сек) Начальная загрузка страницы. Загрузите и отобразите здесь свои критически важные ресурсы.
  2. Бездействие (фрагменты ~ 50 мс) Выполняйте всю несущественную работу, чтобы взаимодействия, которые происходят позже, казались мгновенными. например. ленивая загрузка элементов, выполнение предварительных расчетов анимации и т. д.
  3. Ответ (~ 100 мс) При взаимодействии ответ в течение 100 мс.
  4. Анимация (~ 16 мс) На самом деле мы получаем ~ 12 мс, поскольку у браузера есть некоторые накладные расходы.

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.
    • Кроме того, если вы запускаете цикл анимации на вкладке, которая не отображается, браузер не будет поддерживать ее работу, что означает меньшее использование ЦП, графического процессора и памяти, что приведет к гораздо более длительному времени автономной работы.
  • Браузер должен отображать кадры со скоростью 60 кадров в секунду, т.е. 16 мс / кадр.
    • Из-за накладных расходов браузера мы получаем около 10 мс, поэтому у JS есть время около 3 мс.
  • JavaScript -> Стиль -> Макет -> Рисование -> Составной
  • Не используйте setTimout или setInterval для анимации, так как JS-движок не обращает внимания на конвейер рендеринга при выполнении этого.
  • Для IE9 - используйте requestAnimationFrame с Polyfill.

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

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

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

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

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

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)

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

создано: 2020-08-24
обновлено: 2021-03-13
132265



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей



Комментарии


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

Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)

Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)