События жизненного цикла страницы в JavaScript
События жизненного цикла страницы: DOMContentLoaded, load, beforeunload, unload — когда срабатывают и как использовать.
Каждая страница, открываемая браузером, проходит через предсказуемую последовательность этапов: HTML разбирается, загружаются внешние ресурсы, пользователь взаимодействует со страницей, и в конечном счёте она закрывается. JavaScript предоставляет доступ к этой последовательности через четыре события жизненного цикла страницы — DOMContentLoaded, load, beforeunload и unload. Понимание того, когда именно срабатывает каждое из них, позволяет запускать код инициализации в самый ранний безопасный момент, откладывать ресурсоёмкие операции до полной готовности страницы и защищать пользователей от потери несохранённых данных.
В этом руководстве рассматривается назначение каждого события, порядок их срабатывания, типичные ловушки и современные альтернативы для событий, которые браузеры теперь не рекомендуют использовать. Все примеры ниже можно запустить — вставьте их в HTML-файл или откройте во встроенном редакторе.
События жизненного цикла: обзор
События срабатывают в фиксированном порядке. Первые два происходят при загрузке страницы; последние два — когда пользователь уходит:
| Событие | Цель | Когда срабатывает | Типичное применение |
|---|---|---|---|
DOMContentLoaded | document | HTML полностью разобран; скрипты выполнены; изображения/стили могут ещё загружаться | Инициализация UI, подключение обработчиков, работа с DOM |
load | window | Страница и все подресурсы (изображения, стили, iframe) загружены | Чтение размеров изображений, код, требующий полного присутствия ресурсов |
beforeunload | window | Пользователь собирается покинуть страницу | Предупреждение о несохранённых изменениях |
unload | window | Страница закрывается (устаревшее) | Устаревшая очистка / аналитика |
Главный вывод: DOMContentLoaded — это событие, которое нужно в большинстве случаев. Оно срабатывает значительно раньше load, поэтому привязка кода к нему делает страницу интерактивной быстрее.
Подробный разбор события DOMContentLoaded
Событие DOMContentLoaded срабатывает сразу после того, как HTML-документ полностью разобран, — обычно задолго до того, как загрузятся изображения, стили и другие внешние ресурсы. В этот момент дерево DOM уже построено, поэтому document.getElementById, querySelector и другие методы найдут все элементы, описанные в HTML.
Поскольку событие не ждёт загрузки изображений и CSS, используйте DOMContentLoaded всякий раз, когда скрипту нужна только структура DOM — а это подавляющее большинство случаев.
Как скрипты влияют на DOMContentLoaded
Браузер приостанавливает разбор HTML при встрече обычного тега <script>, выполняет скрипт и лишь затем продолжает. Это означает, что DOMContentLoaded ожидает синхронные скрипты. Есть два важных нюанса:
<script async>выполняется сразу после загрузки и не блокируетDOMContentLoaded.<script defer>выполняется после разбора документа, но до срабатыванияDOMContentLoaded, в порядке их следования в документе.
Если вы управляете порядком выполнения скриптов с помощью этих атрибутов, полная картина описана в разделе scripts: async, defer.
Проверка состояния через document.readyState
Если ваш код может выполниться после того, как DOM уже разобран (например, скрипт, добавленный динамически), обработчик события никогда не вызовется, поскольку событие уже произошло. Защититесь от этого с помощью document.readyState:
function onReady() {
console.log('DOM is ready, current state:', document.readyState);
}
if (document.readyState === 'loading') {
// Still parsing — wait for the event.
document.addEventListener('DOMContentLoaded', onReady);
} else {
// 'interactive' or 'complete' — DOM is already ready.
onReady();
}readyState проходит через три значения: "loading" во время разбора, "interactive" когда DOM готов (в этот момент срабатывает DOMContentLoaded), и "complete" когда всё, включая ресурсы, загружено (в этот момент срабатывает load).
Интерактивный пример: DOMContentLoaded в действии
Чтобы увидеть DOMContentLoaded в действии, следующий пример обновляет содержимое элемента div после полного разбора HTML-документа, но до завершения загрузки всей страницы.
<!DOCTYPE html>
<html lang="en">
<head>
<title>DOMContentLoaded Example</title>
</head>
<body>
<div id="output">Waiting for DOM...</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('output').innerHTML = 'DOM fully loaded and parsed!'
});
</script>
</body>
</html>Этот пример можно вставить в любой HTML-файл и открыть в браузере, чтобы увидеть, насколько быстро срабатывает DOMContentLoaded по сравнению с полной загрузкой страницы.
Исследование события load
Событие load необходимо для операций, требующих полной загрузки веб-страницы, включая все зависимые ресурсы — изображения и стили. К моменту срабатывания load каждое изображение имеет реальные размеры, шрифты применены, а iframe загружены — поэтому именно здесь следует размещать код, измеряющий отрендеренную страницу.
Компромисс заключается в тайминге: одно большое изображение или медленный сторонний виджет задержит load для всей страницы. Именно поэтому следует использовать load только для работы, действительно зависящей от ресурсов, а обычную инициализацию оставлять на DOMContentLoaded. Для обработки загрузки и ошибок отдельных ресурсов (а не всей страницы) см. resource loading: onload and onerror.
Интерактивный пример: демонстрация события load
Здесь мы создаём пример, в котором сообщение отображается только после полной загрузки всего содержимого страницы. Как видите, событие DOMContentLoaded всегда происходит раньше события load.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load Event Example</title>
</head>
<body>
<div>You see first triggered event on top of the other.</div>
<script>
window.addEventListener('load', function() {
const newDiv = document.createElement("div");
newDiv.innerHTML = 'loaded event happened!'
document.body.append(newDiv);
});
window.addEventListener('DOMContentLoaded', function() {
const newDiv = document.createElement("div");
newDiv.innerHTML = 'DOMContentLoaded event happened!'
document.body.append(newDiv);
});
</script>
</body>
</html>Скопируйте и проверьте этот код в своём HTML-окружении, чтобы увидеть разницу в тайминге между DOMContentLoaded и load.
Работа с событием beforeunload
Событие beforeunload чрезвычайно полезно для предотвращения потери данных — оно предупреждает пользователей перед тем, как они покинут страницу с несохранёнными изменениями. Когда обработчик устанавливает event.returnValue, браузер показывает стандартный диалог подтверждения («Покинуть сайт? Изменения могут не сохраниться»).
Несколько правил, которые браузеры теперь применяют принудительно, — стоит знать их перед использованием:
- Пользовательское сообщение игнорируется. Для борьбы со спамом браузеры отображают собственный текст вне зависимости от строки, которую вы назначаете. Достаточно установить
returnValueв любое непустое значение (или вызватьevent.preventDefault()), чтобы вызвать диалог. - Диалог появляется только если пользователь взаимодействовал со страницей (кликал, печатал и т. д.). Страница, с которой пользователь не взаимодействовал, не может блокировать навигацию.
- Регистрируйте обработчик только при наличии реально несохранённых изменений и удаляйте его после сохранения данных. Постоянный обработчик
beforeunloadраздражает пользователей и может ухудшить производительность кеша назад/вперёд.
Более надёжный паттерн добавляет и удаляет обработчик на основе флага изменений:
let isDirty = false;
function beforeUnloadHandler(event) {
event.preventDefault();
// Required for some browsers to show the dialog.
event.returnValue = '';
}
function markDirty() {
if (!isDirty) {
isDirty = true;
window.addEventListener('beforeunload', beforeUnloadHandler);
}
}
function markSaved() {
isDirty = false;
window.removeEventListener('beforeunload', beforeUnloadHandler);
}Интерактивный пример: реализация подтверждения через beforeunload
Этот пример показывает пользователю диалог подтверждения при попытке покинуть страницу, помогая предотвратить случайную потерю данных.
<!DOCTYPE html>
<html lang="en">
<head>
<title>beforeunload Example</title>
</head>
<body>
<p>Now if you want to exit this page, a confirmation alert will be shown as a result of the <code>beforeunload</code> event.</p>
<script>
window.addEventListener('beforeunload', function(event) {
event.returnValue = '';
});
</script>
</body>
</html>Проверьте этот код, открыв страницу «попробуй сам» и попытавшись перейти на другую страницу.
Использование события unload
The unload event is deprecated and you won't be able to use it in most modern browsers. It is also considered a bad practice. Therefore this part is only for better information about legacy codes.
Хотя событие unload сейчас редко используется из-за ограничений современных браузеров и распространения одностраничных приложений, исторически оно срабатывало при закрытии страницы — это было полезно для освобождения ресурсов или отправки последнего аналитического запроса. Проблема в том, что unload (и beforeunload) не позволяют браузеру сохранять страницу в кеше назад/вперёд (bfcache), что замедляет навигацию по кнопке «Назад» для всех пользователей.
Современные альтернативы: visibilitychange и pagehide
Вместо unload следует слушать visibilitychange и обрабатывать переход в состояние "hidden" как последний надёжный момент для сохранения состояния. Это событие стабильно срабатывает на десктопе и мобильных устройствах, в том числе когда пользователь переключает вкладки или закрывает приложение:
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
// Last reliable point to save state.
console.log('Page hidden — flush analytics / save draft here');
}
});Для отправки данных при выходе используйте navigator.sendBeacon(), который ставит запрос в очередь и гарантирует его отправку даже после закрытия страницы:
function sendStats(data) {
navigator.sendBeacon('/log', JSON.stringify(data));
}Интерактивный пример: использование события unload
Этот код показывает сообщение alert при закрытии страницы. Как упоминалось, событие устарело и не работает в большинстве современных браузеров — предпочтительнее использовать visibilitychange.
<!DOCTYPE html>
<html lang="en">
<head>
<title>unload Example</title>
</head>
<body>
<script>
window.addEventListener('unload', function(event) {
alert('Page is unloading...');
// Perform cleanup tasks or analytics here
});
</script>
</body>
</html>Заключение
Каждое из этих событий соответствует определённой фазе жизненного цикла веб-страницы — от начальной загрузки до окончательного закрытия. Практические рекомендации кратки:
- Выполняйте большую часть инициализации в
DOMContentLoaded— оно срабатывает раньше всего, и DOM уже готов. - Используйте
loadтолько когда вам необходимо дождаться полной загрузки изображений, шрифтов или других подресурсов. - Используйте
beforeunloadдля защиты несохранённых изменений, добавляя обработчик только при наличии несохранённых данных. - Избегайте
unload; вместо него используйтеvisibilitychangeвместе сnavigator.sendBeacon().
Для более глубокого изучения обратитесь к связанным разделам:
- Introduction to browser events
- Scripts: async, defer
- Resource loading: onload and onerror
- Browser environment and specs