Жизненный цикл страницы: DOMContentLoaded, load, beforeunload, unload

Жизненный цикл HTML-страницы состоит из трех важных событий:

  • DOMContentLoaded - браузер полностью загрузил HTML-код страницы и построил дерево DOM. Но внешние ресурсы, такие как изображения <img> и таблицы стилей, могут все еще загружаться;
  • onload JavaScript - браузер загрузил все ресурсы (изображения, стили и т. д.);
  • beforeunload/unload - когда пользователь покидает страницу.

Каждое событие может использоваться для различных целей:

  • Событие DOMContentLoaded - DOM готов, поэтому обработчик может искать узлы DOM, инициализировать интерфейс.
  • Событие load - загружаются дополнительные ресурсы, и можно получать размеры изображений (если это не указано в HTML / CSS) и т. д.
  • Событие beforeunload/unload - пользователь покидает страницу. Можно проверить, сохранил ли пользователь изменения, и спросить его, действительно ли он хочет уйти.

Рассмотрим эти события подробнее.

DOMContentLoaded

Событие DOMContentLoaded происходит в объекте документа. Необходимо использовать addEventListener, чтобы отследить его:

document.addEventListener("DOMContentLoaded", ready);

Например:

<script>
  function ready() {
    alert('DOM is ready');

    // изображения еще не загрузились (если только не были сохранены в кэше), поэтому их размер 0x0
    alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  }

  document.addEventListener("DOMContentLoaded", ready);
</script>

<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

В этом примере обработчик DOMContentLoaded запускается при загрузке документа (не при JavaScript onload), и не ждет полной загрузки страницы. Таким образом, предупреждение сообщает, что размер изображений равен нулю.

На первый взгляд событие DOMContentLoaded простое. Но есть несколько особенностей.

DOMContentLoaded и скрипты

Когда браузер загружает HTML и встречает в коде <script> ... </ script>, он не может продолжить создание DOM. Он должен выполнить скрипт прямо сейчас. Поэтому DOMContentLoaded может произойти только после выполнения всех скриптов.

Внешние скрипты (подключаемые с помощью src) также приостанавливают создание DOM, пока скрипт загружается и выполняется. Поэтому DOMContentLoaded ожидает выполнения внешних скриптов.

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

Несколько слов об async и defer

Атрибуты async и defer в window onload JavaScript используются только для внешних скриптов. Они игнорируются, если нет подключения через src.

Оба атрибута указывают браузеру, что он может продолжать обрабатывать страницу и загружать скрипты «в фоновом режиме». А затем запускать скрипт после его полной загрузки. Таким образом, скрипт не блокирует создание DOM и отображение страниц.

Между этими атрибутами есть два отличия.

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

Атрибут async используется для полностью независимых скриптов.

DOMContentLoaded и стили

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

<link rel="stylesheet" href="style.css">
<script>
  // скрипт не будет выполняться до тех пор, пока не загружены таблицы стиле
  alert(getComputedStyle(document.body).marginTop);
</script>

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

Поскольку DOMContentLoaded ожидает загрузки скриптов (document onload JavaScript), он должен дождаться и загрузки стилей.

Автоматическое заполнение браузерами

Firefox, Chrome и Opera автоматически заполняют поля форм для DOMContentLoaded. Например, если страница имеет форму с полями для ввода имени пользователя и пароля, а браузер запомнил их значения, DOMContentLoaded может попытаться автоматически их заполнить (если это одобрено пользователем).

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

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

window.onload

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

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

<script>
  window.onload = function() {
    alert('Page loaded');

    // на данный момент изображения уже загружены
    alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
  };
</script>

<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

window.onunload

Когда посетитель покидает страницу, для объекта window запускается событие unload. При этом можно выполнить определенные действия, не связанные с задержкой. Например, закрыть всплывающие окна. Но нельзя отменить переход на другую страницу. Для этого необходимо использовать другое событие - onbeforeunload.

window.onbeforeunload

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

Например:

window.onbeforeunload = function() {
  return "Некоторые изменения не были сохранены. Вы все равно желаете покинуть страницу?";
};

Некоторые браузеры, такие как Chrome и Firefox, игнорируют заданное значение и отображают собственное сообщение. Делается это из соображений безопасности, чтобы защитить пользователя от мошеннических и хакерских сообщений при наступлении события JavaScript onload.

readyState

Что произойдет, если установить обработчик DOMContentLoaded после загрузки документа? Естественно, он никогда не запустится.

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

Свойство document.readyState предоставляет такую информацию. Возможны три значения:

  • "loading" - документ загружается;
  • "interactive" - документ полностью считан;
  • "complete" - документ полностью считан и все ресурсы (например, изображения) загружены.

Так можно проверить значение document.readyState и настроить обработчик или выполнить код немедленно, как только он будет готов.

Например, следующим образом:

function work() { /*...*/ }
if (document.readyState == 'loading') {
  document.addEventListener('DOMContentLoaded', work);
} else {
  work();
}

Событие readystatechange запускается при изменении состояния. Можно выводить все эти состояния следующим образом:

// текущее состояние
console.log(document.readyState);

// выводим изменение состояния
document.addEventListener('readystatechange', () => console.log(document.readyState));

Событие readystatechange является альтернативным методом отслеживания состояния загрузки документа, оно было введено давно. В настоящее время оно редко используется, но рассмотрим его для полноты картины.

Как размещается readystatechange по отношению к другим событиям? Чтобы продемонстрировать порядок их срабатывания, ниже приводится пример с <iframe>, <img> и обработчиками, которые регистрируют события (JavaScript onload и другие):

<script>
  function log(text) { /* выводим время и сообщение */ }
  log('initial readyState:' + document.readyState);

  document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
  document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded'));

  window.onload = () => log('window onload');
</script>

<iframe src="iframe.html" onload="log('iframe onload')"></iframe>

<img src="http://en.js.cx/clipart/train.gif" id="img">
<script>
  img.onload = () => log('img onload');
</script>

Демо-версию можно найти на sandbox.

Стандартная последовательность событий:

  1. [1] инициализация readyState:loading;
  2. [2] readyState:interactive;
  3. [2] DOMContentLoaded;
  4. [3] iframe onload;
  5. [4] readyState:complete;
  6. [4] img onload;
  7. [4] window onload JavaScript.

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

Document.readyState принимает состояние interactive непосредственно перед DOMContentLoaded. Эти два события фактически означают одно и то же.

Document.readyState завершается, когда загружаются все ресурсы (iframe и img). Мы видим, что это происходит примерно в то же время, что и img.onload (img - последний ресурс) и window.onload. Переключение на состояние complete означает то же, что и window.onload. Разница в том, что window.onload всегда запускается после всех других обработчиков load.

Заключение

События жизненного цикла страницы:

  • DOMContentLoaded инициирует события в документе, когда DOM готов. На этом этапе можно применить к элементам JavaScript:

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

  • Когда страница и ресурсы загружены, для объекта window запускается событие load. Мы редко его используем, потому что обычно не нужно так долго ждать;
  • Событие JavaScript onload запускается для объекта window, когда пользователь хочет покинуть страницу. Если при этом возвращается строка, браузер отображает сообщение с вопросом о том, действительно ли пользователь хочет покинуть страницу;
  • Событие unload запускается для объекта window, когда пользователь окончательно покидает страницу. В обработчике можно выполнять простые действия, которые не связаны с задержками или запросом пользователя. Из-за этого ограничения данное событие используется редко;
  • Document.readyState - это текущее состояние документа, изменения могут отслеживаться в событии readystatechange:

- loading - документ загружается;
- interactive - документ анализируется. Это происходит примерно в то же время, что и событие DOMContentLoaded, но непосредственно перед ним;
- complete - документ и все ресурсы загружены. Происходит примерно в то же время, что и событие window onload JavaScript, но непосредственно перед ним.

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

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