document ready js без jQuery и события жизненного цикла страницы

Для запуска кода после готовности DOM (document ready js) используйте DOMContentLoaded и проверку document.readyState. Для размеров изображений нужен load, а при уходе пригодятся pagehide и navigator.sendBeacon. Вы узнаете, как заменить $(document).ready, выбрать async, defer или type="module" и предупредить о несохраненной форме.

Жизненный цикл HTML-страницы

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

  • DOMContentLoaded. Объект document сообщает, что DOM построен и отложенные скрипты выполнены.
  • load. Объект window сообщает, что загрузились ресурсы, которые задерживают событие.
  • beforeunload. Объект window позволяет запросить предупреждение о несохраненных изменениях.
  • pagehide. Объект window сообщает, что страница покидает экран, в том числе перед сохранением в bfcache.
  • visibilitychange. Объект document сообщает об изменении видимости вкладки.

Для инициализации формы выбирайте DOMContentLoaded. Для размеров картинок ждите load у изображения или window, а небольшие данные отправляйте через pagehide и sendBeacon.

document ready js: запуск кода после готовности DOM

Запрос document ready js обычно означает запуск функции после построения DOM. В jQuery эту задачу решал $(document).ready, но чистый JavaScript закрывает ее без библиотеки.

Поместите кнопку и подключение файла в HTML:

<button id="save" type="button">Сохранить</button>
<script src="app.js" defer></script>

В app.js добавьте проверку readyState и обработчик кнопки:

function init() {
  const button = document.getElementById('save');
  button.addEventListener('click', () => {
    console.log('Форма готова к отправке');
  });
}

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', init, { once: true });
} else {
  init();
}

При состоянии loading код ждет DOMContentLoaded. При состоянии interactive или complete функция запускается сразу, поэтому шаблон подходит и для поздно загруженного файла.

Так работает и javascript document ready без jQuery. После нажатия кнопки консоль покажет сообщение «Форма готова к отправке».

DOMContentLoaded и порядок выполнения скриптов

Обычный внешний script останавливает разбор HTML в месте подключения. Браузер загружает файл, выполняет его и продолжает строить DOM.

<script src="app.js"></script>

DOMContentLoaded ждет обычные скрипты и скрипты с defer. Независимый async-файл событие не задерживает: браузер запускает его сразу после загрузки, не соблюдая общий порядок.

Скрипт в head может обратиться к элементу, которого браузер еще не встретил. Вызов document.getElementById тогда вернет null. Поставьте defer или перенесите подключение обычного файла перед закрывающим body.

Скрипт может подписаться на DOMContentLoaded слишком поздно. Если событие уже прошло, обработчик не сработает. Проверка document.readyState из предыдущего примера закрывает оба сценария.

async, defer и type="module": когда выполняются скрипты

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

ВариантПорядок выполненияСвязь с DOMContentLoadedПодходит для
Обычный scriptВ месте подключения после загрузки файлаСобытие ждет выполнениеКода, которому нужен текущий фрагмент HTML
asyncСразу после загрузки файла, порядок между файлами не гарантированСобытие не ждет такой скриптНезависимой аналитики
deferПосле разбора HTML, в порядке подключенияСобытие ждет выполнение defer-файловОсновного кода интерфейса
type="module"Отложенно после разбора HTML, без asyncСобытие ждет выполнение модуляКода с import и export

Берите async для полностью самостоятельного файла. Используйте defer, если скрипты должны сохранить порядок. Модуль подключайте так:

<script type="module" src="app.js"></script>

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

Правила атрибутов описывает документация MDN по элементу script.

DOMContentLoaded и таблицы стилей

DOMContentLoaded не ждет загрузку CSS напрямую. Однако блокирующая таблица стилей может задержать выполнение скрипта после link.

<link rel="stylesheet" href="style.css">
<script>
  // Скрипт ждет загрузки таблицы стилей.
  const margin = getComputedStyle(document.body).marginTop;
  console.log(margin);
</script>

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

Ошибки при инициализации скриптов

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

Скрипт в head получает null

Обычный script в head выполняется до чтения body. Вызов document.getElementById('menu') вернет null, если элемент menu еще не попал в DOM.

Добавьте defer внешнему файлу или перенесите обычный script перед закрывающим body. После исправления обработчик найдет элемент.

Обработчик добавлен после события

Async-файл может выполниться после DOMContentLoaded. Новый обработчик уже не сработает, если код подписался на прошедшее событие без проверки readyState.

При loading дождитесь DOMContentLoaded, а при interactive или complete вызовите init сразу. Так один шаблон работает при разных скоростях загрузки.

Картинка имеет нулевой размер

DOMContentLoaded не обязан ждать изображения. В этот момент offsetWidth и offsetHeight могут быть равны нулю, если картинка еще не загрузилась.

Для естественных размеров используйте load у изображения или window load. Задайте ширину и высоту в HTML либо CSS, чтобы верстка не ждала расчета размеров.

window onload js: как дождаться загрузки изображений

Событие load срабатывает на window после загрузки страницы и ресурсов, которые задерживают событие. Поэтому запрос window onload js или window onload javascript относится к обработчику window.onload.

Событие загрузки всей страницы приходит на window. document.onload для этой задачи не сработает.

Сохраните рядом с HTML изображение под именем preview.jpg. Создайте HTML с картинкой и подпишитесь на load:

<img id="preview" src="preview.jpg" alt="Изображение для проверки размеров">
<script>
  window.addEventListener('load', () => {
    const image = document.getElementById('preview');
    console.log(`${image.naturalWidth}x${image.naturalHeight}`);
  });
</script>

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

Если нужна одна картинка, подпишитесь на ее собственное событие:

const image = document.createElement('img');
image.alt = 'Изображение для проверки размеров';

image.addEventListener('load', () => {
  console.log(image.naturalWidth, image.naturalHeight);
});
image.addEventListener('error', () => {
  console.log('Изображение не загрузилось');
});

image.src = 'preview.jpg';
document.body.append(image);

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

Запрос js document onload часто означает ожидание всей страницы. Для этого добавьте обработчик load на window через addEventListener.

window.onbeforeunload: предупреждение о несохраненных изменениях

Используйте beforeunload только при реальных несохраненных данных. Современные браузеры игнорируют ваш текст и показывают стандартный диалог.

Сохраните пример как form.html и откройте файл в браузере. Скрипт расположен после формы, поэтому ее элементы уже доступны:

<form id="profile-form">
  <label>
    О себе
    <textarea name="about"></textarea>
  </label>
</form>
<script>
  let hasUnsavedChanges = false;
  const form = document.getElementById('profile-form');

  form.addEventListener('input', () => {
    hasUnsavedChanges = true;
  });

  window.addEventListener('beforeunload', (event) => {
    if (!hasUnsavedChanges) {
      return;
    }

    event.preventDefault();
    event.returnValue = true;
  });
</script>

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

event.preventDefault() запрашивает подтверждение ухода. Свойство event.returnValue сохраняет совместимость со старыми браузерами.

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

После успешного сохранения присвойте hasUnsavedChanges значение false, чтобы предупреждение не мешало обычному переходу.

MDN описывает ограничения события beforeunload, включая условие пользовательского взаимодействия.

pagehide, visibilitychange и отправка данных

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

Для перехода со страницы подходит pagehide. Для сохранения черновика при скрытии вкладки подходит visibilitychange со значением hidden. Скрытие вкладки само по себе не означает завершение посещения.

Небольшие данные отправляйте через navigator.sendBeacon. Метод ставит POST-запрос в очередь и не заставляет страницу ждать ответ.

function sendVisitEnd() {
  const data = JSON.stringify({
    page: location.pathname,
    endedAt: Date.now()
  });

  navigator.sendBeacon(
    '/visit-end.php',
    new Blob([data], { type: 'application/json' })
  );
}

window.addEventListener('pagehide', sendVisitEnd);

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    saveDraft();
  }
});

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

Локальная проверка sendBeacon на PHP

Для локального теста нужны страница index.html и принимающий файл visit-end.php. Сохраните их в одной папке.

Страница и обработчик

В index.html добавьте ссылку и код отправки:

<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <title>Проверка sendBeacon</title>
</head>
<body>
  <a href="/?next=1">Перейти на другую страницу</a>
  <script>
    function sendVisitEnd() {
      const data = JSON.stringify({
        page: location.pathname,
        endedAt: Date.now()
      });

      navigator.sendBeacon(
        '/visit-end.php',
        new Blob([data], { type: 'application/json' })
      );
    }

    window.addEventListener('pagehide', sendVisitEnd);
  </script>
</body>
</html>

В visit-end.php примите POST-запрос и запишите данные в журнал:

<?php
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    header('Allow: POST');
    exit;
}

$data = json_decode(file_get_contents('php://input'), true);

if (!is_array($data)
    || !isset($data['page'], $data['endedAt'])
    || !is_string($data['page'])
    || !is_numeric($data['endedAt'])) {
    http_response_code(400);
    exit;
}

error_log('visit-end: ' . json_encode([
    'page' => $data['page'],
    'endedAt' => $data['endedAt']
]));

http_response_code(204);

Адрес /visit-end.php в sendBeacon соответствует файлу в корне сервера.

Запуск встроенного сервера

Откройте терминал в папке с файлами и выполните php -v. Если команда выводит сведения об интерпретаторе, запустите сервер:

php -d error_log= -S 127.0.0.1:8000

Введите в браузере http://127.0.0.1:8000/. Страница покажет ссылку «Перейти на другую страницу».

  1. Нажмите ссылку на странице. Адрес изменится на http://127.0.0.1:8000/?next=1.
  2. Посмотрите терминал. В нем появится строка visit-end: с полями page и endedAt.

Если строки нет, проверьте наличие обоих файлов в папке сервера. Ошибка 404 для visit-end.php означает неверное расположение файла; ответ 400 означает, что обработчик отверг тело запроса.

Не открывайте index.html двойным щелчком: адрес file:// не запускает PHP. Для остановки локального сервера нажмите Ctrl+C в его терминале. Встроенный сервер PHP используйте только для разработки.

pageshow, pagehide и возврат через bfcache

Кнопка «Назад» может вернуть страницу из bfcache без нового разбора HTML. При таком возврате DOMContentLoaded и load повторно не срабатывают.

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    console.log('Страница восстановлена из bfcache');
  }
});

window.addEventListener('pagehide', (event) => {
  console.log('Страница покидает экран:', event.persisted);
});

Используйте pageshow с event.persisted, когда данные нужно обновить после возврата. Значение true у pagehide означает, что браузер помещает страницу в bfcache.

readyState и последовательность событий

Свойство document.readyState дает 3 состояния. Они помогают выбрать момент запуска, когда способ подключения файла заранее неизвестен.

  • loading. Браузер еще читает HTML.
  • interactive. DOM построен, но ресурсы могут загружаться.
  • complete. Документ и ресурсы, задерживающие load, загрузились.
function log(message) {
  console.log(`${performance.now().toFixed(1)} ms: ${message}`);
}

log(`initial: ${document.readyState}`);

document.addEventListener('readystatechange', () => {
  log(`readyState: ${document.readyState}`);
});

document.addEventListener('DOMContentLoaded', () => {
  log('DOMContentLoaded');
});

window.addEventListener('load', () => {
  log('window load');
});

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

Браузер переходит из loading в interactive после разбора HTML. Затем он выполняет отложенные скрипты и отправляет DOMContentLoaded. После ресурсов состояние меняется на complete, затем window получает load.

События страницы: что выбрать для задачи

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

СобытиеОбъектКогда срабатываетДля чего использовать
DOMContentLoadeddocumentDOM построен, обычные отложенные скрипты выполненыИнициализации элементов
loadwindowЗагрузились страница и ресурсы, задерживающие событиеИзмерения загруженных изображений
beforeunloadwindowПеред уходом со страницыПредупреждения о несохраненных данных
pagehidewindowСтраница покидает экранСохранения состояния и sendBeacon
pageshowwindowСтраница открывается или восстанавливаетсяОбновления после bfcache
visibilitychangedocumentМеняется видимость вкладкиСохранения черновика при скрытии

Для javascript document ready выбирайте DOMContentLoaded или проверку readyState. Для изображения используйте его load, для всей страницы – window load. Перед уходом предупреждайте только о реальных изменениях, а данные отправляйте через pagehide и navigator.sendBeacon.

Совет от нашей редакции: начинайте инициализацию с DOMContentLoaded, а load оставляйте для задач, которым нужны размеры и другие загруженные ресурсы.

Комментарии

Оставьте свой комментарий
E
Evgeniy

Ошибка:
"Скрипты с атрибутом defer выполняются после того, как документ будет полностью загружен и обработан (если необходимо они ожидают завершения процесса), сразу после события DOMContentLoaded."

Скрипты вызываются перед DOMContentLoaded.