Веб

Управление выполнением скриптов при загрузке страницы в JavaScript

Пошаговое руководство по управлению несколькими скриптами при загрузке страницы в JavaScript. Использование DOMContentLoaded, async, defer и других методов.

4 ответа 1 просмотр

Как правильно управлять выполнением нескольких скриптов при загрузке страницы в чистом JavaScript? Как обеспечить выполнение второго скрипта после полной загрузки DOM, если событие DOMContentLoaded срабатывает только один раз? Какие есть альтернативы повторной отправке события DOMContentLoaded для избежания рекурсии?

Для управления выполнением нескольких скриптов при загрузке страницы в чистом JavaScript следует использовать событие DOMContentLoaded и различные подходы к синхронизации. Основной принцип заключается в том, что событие DOMContentLoaded срабатывает только один раз, поэтому для выполнения второго скрипта после полной загрузки DOM можно добавить обработчики к тому же событию, использовать Promise или атрибуты defer и async в тегах скриптов.


Содержание


Основные события загрузки страницы

При работе с JavaScript на веб-странице важно понимать ключевые события жизненного цикла загрузки. Основные события события загрузки, которые используются для управления выполнением скриптов:

  • DOMContentLoaded — срабатывает, когда HTML-документ полностью загружен и обработан, но внешние ресурсы (изображения, стили, фреймы) могут еще не загружены
  • load — срабатывает, когда вся страница вместе со всеми ресурсами полностью загружена
  • readystatechange — событие для отслеживания состояния загрузки документа

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


Управление несколькими скриптами с помощью DOMContentLoaded

Событие DOMContentLoaded срабатывает только один раз, поэтому для управления несколькими скриптами необходимо использовать другие подходы. Вот основные методы:

Множественные обработчики одного события

Вы можете добавить несколько обработчиков к одному событию DOMContentLoaded:

javascript
document.addEventListener("DOMContentLoaded", () => {
 // Первый скрипт
 initFirstScript();
});

document.addEventListener("DOMContentLoaded", () => {
 // Второй скрипт
 initSecondScript();
});

Проверка состояния документа

Если скрипт может загружаться после того, как событие уже произошло, можно проверить document.readyState:

javascript
function runAfterDOM(fn) {
 if (document.readyState === 'loading') {
 document.addEventListener('DOMContentLoaded', fn);
 } else {
 fn();
 }
}

runAfterDOM(() => {
 // Код, который должен выполниться после DOM готов
});

Использование Promise

Для более сложной синхронизации можно использовать Promise:

javascript
const domReady = new Promise(resolve => {
 if (document.readyState === "loading") {
 document.addEventListener("DOMContentLoaded", resolve);
 } else {
 resolve();
 }
});

domReady.then(() => {
 // Код, который должен выполниться после DOM готов
});

Альтернативы повторной отправке DOMContentLoaded

Чтобы избежать рекурсии и дублирования событий, существуют альтернативные подходы к управлению выполнением нескольких javascript скрипт:

Пользовательские события

Создайте собственное событие, которое вы можете триггерить после выполнения первого скрипта:

javascript
// Первый скрипт
document.addEventListener('DOMContentLoaded', () => {
 initFirstScript();
 
 // Триггерим пользовательское событие
 const event = new CustomEvent('firstScriptLoaded', { detail: {} });
 document.dispatchEvent(event);
});

// Второй скрипт
document.addEventListener('firstScriptLoaded', () => {
 initSecondScript();
});

Прямое вызов функций

Просто вызывайте второй скрипт из первого:

javascript
document.addEventListener('DOMContentLoaded', () => {
 initFirstScript();
 initSecondScript(); // Выполняется сразу после первого
});

Использование флагов

javascript
let domReady = false;

document.addEventListener('DOMContentLoaded', () => {
 domReady = true;
 initFirstScript();
 initSecondScript();
});

// Если скрипт загружается позже
if (domReady) {
 initSecondScript();
}

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


Атрибуты async и defer для управления загрузкой

Для оптимальной отложенная загрузка скриптов существуют атрибуты async и defer:

Атрибут async

html
<script async src="first.js"></script>
<script async src="second.js"></script>

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

Атрибут defer

html
<script defer src="first.js"></script>
<script defer src="second.js"></script>

Скрипты с defer загружаются параллельно, но выполняются последовательно после полной загрузки DOM и в порядке их расположения в документе. Идеально для скриптов, зависящих от DOM.

Сравнение атрибутов

Атрибут Загрузка Выполнение Порядок Блокировка рендеринга
Без атрибута Последовательно По порядку Да Да
async Параллельно По завершении загрузки Нет Нет
defer Параллельно После DOM, по порядку Да Нет

Эти атрибуты позволяют эффективно управлять загрузкой файла скрипт без сложного JavaScript-кода.


Пользовательские события и Promise для синхронизации скриптов

Для сложных сценариев синхронизации нескольких загрузка скриптов javascript можно использовать продвинутые подходы:

Синхронизация с Promise

javascript
function loadScript(url) {
 return new Promise((resolve, reject) => {
 const script = document.createElement('script');
 script.src = url;
 script.onload = resolve;
 script.onerror = reject;
 document.head.appendChild(script);
 });
}

// Использование
loadScript('first.js').then(() => {
 console.log('Первый скрипт загружен');
 return loadScript('second.js');
}).then(() => {
 console.log('Второй скрипт загружен');
});

Манипуляция событиями загрузки

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

javascript
// Создаем глобальный объект для управления скриптами
const ScriptLoader = {
 scripts: [],
 load(url) {
 return new Promise((resolve, reject) => {
 const script = document.createElement('script');
 script.src = url;
 script.onload = () => {
 this.scripts.push(url);
 resolve();
 };
 script.onerror = reject;
 document.head.appendChild(script);
 });
 },
 loadSequential(urls) {
 return urls.reduce((promise, url) => 
 promise.then(() => this.load(url)), Promise.resolve());
 }
};

// Использование
ScriptLoader.loadSequential(['first.js', 'second.js'])
 .then(() => console.log('Все скрипты загружены'));

Практические примеры реализации

Пример 1: Базовая реализация с DOMContentLoaded

javascript
// Основной скрипт
document.addEventListener('DOMContentLoaded', () => {
 console.log('DOM загружен');
 initializeApp();
});

// Если нужно выполнить код после полной загрузки страницы
window.addEventListener('load', () => {
 console.log('Все ресурсы загружены');
 initializeAnalytics();
});

Пример 2: Управление зависимостями скриптов

javascript
// Функция для загрузки скриптов с зависимостями
function loadScriptWithDependencies(url, dependencies = []) {
 return new Promise((resolve, reject) => {
 // Проверяем зависимости
 Promise.all(dependencies.map(dep => 
 window[dep] ? Promise.resolve() : Promise.reject(`Зависимость ${dep} не найдена`)
 )).then(() => {
 const script = document.createElement('script');
 script.src = url;
 script.onload = resolve;
 script.onerror = reject;
 document.head.appendChild(script);
 }).catch(reject);
 });
}

// Использование
loadScriptWithDependencies('app.js', ['jquery', 'lodash'])
 .then(() => console.log('Приложение загружено'))
 .catch(error => console.error('Ошибка загрузки:', error));

Пример 3: Отложенная загрузка скриптов

javascript
// Функция для отложенной загрузки скрипта
function loadScriptDeferred(url) {
 return new Promise((resolve, reject) => {
 const script = document.createElement('script');
 script.src = url;
 script.defer = true;
 script.onload = resolve;
 script.onerror = reject;
 document.head.appendChild(script);
 });
}

// Использование для **загрузка скрипта после загрузки страницы**
if (document.readyState === 'complete') {
 loadScriptDeferred('analytics.js');
} else {
 window.addEventListener('load', () => {
 loadScriptDeferred('analytics.js');
 });
}

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


Источники

  1. MDN Web Docs — Документация по событию DOMContentLoaded: https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event
  2. javascript.info — Обработка событий загрузки страницы: https://javascript.info/onload-ondomcontentloaded
  3. MDN Web Docs — Документация по тегу script: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

Заключение

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

Ключевое правило — избегайте повторной отправки события DOMContentLoaded, так как оно срабатывает только один раз. Вместо этого используйте описанные альтернативы для синхронизации скриптов. Для оптимальной производительности и лучшего пользовательского опыта рекомендуется использовать атрибут defer для скриптов, которые должны выполняться после полной загрузки DOM, и async для независимых скриптов.

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

MDN contributors / Технический писатель

В MDN описывается, что событие DOMContentLoaded срабатывает один раз, когда HTML полностью разобран и все отложенные скрипты выполнены. Чтобы гарантировать, что второй скрипт выполнится после полной загрузки DOM, можно просто добавить его обработчик к тому же событию, либо использовать Promise, который резолвится в момент DOMContentLoaded. Например:

javascript
document.addEventListener("DOMContentLoaded", () => {
 // первый скрипт
});

const domReady = new Promise(resolve => {
 if (document.readyState === "loading") {
 document.addEventListener("DOMContentLoaded", resolve);
 } else {
 resolve();
 }
});

domReady.then(() => {
 // второй скрипт
});

Если скрипт может загружаться позже, можно проверить document.readyState и сразу вызвать нужную функцию, если событие уже произошло. Для полной загрузки всех ресурсов, включая изображения, используйте событие load окна: window.addEventListener("load", () => { /* код */ });. Альтернативой повторной отправки DOMContentLoaded является создание собственного события, которое вы можете триггерить после выполнения первого скрипта, а второй скрипт будет слушать это событие. Также можно использовать атрибуты defer и type="module" в <script> тегах, чтобы браузер гарантировал выполнение скриптов после парсинга DOM.

Ilya Kantor / Автор, тренер, JS-разработчик

Для управления выполнением нескольких скриптов при загрузке страницы в чистом JavaScript обычно используют событие DOMContentLoaded. Вы можете добавить к нему несколько обработчиков – каждый из них выполнится, когда DOM будет готов. Если второй скрипт должен запускаться только после того, как первый полностью завершил работу, можно внутри обработчика первого вызвать второй скрипт или же использовать Promise/async‑await. Например:

javascript
document.addEventListener('DOMContentLoaded', () => {
 initFirst(); // первый скрипт
 initSecond(); // второй скрипт после первого
});

Если скрипт загружается после того, как событие уже произошло, можно проверить document.readyState и вызвать функцию сразу:

javascript
function runAfterDOM(fn) {
 if (document.readyState === 'loading') {
 document.addEventListener('DOMContentLoaded', fn);
 } else {
 fn();
 }
}

Для полной загрузки всех ресурсов можно использовать событие load на window:

javascript
window.addEventListener('load', () => {
 // код после полной загрузки
});

Также удобно использовать атрибут defer в теге <script>, чтобы скрипт исполнялся после парсинга DOM, но до события load. При необходимости синхронизации нескольких скриптов можно использовать пользовательские события или Promise, чтобы избежать рекурсии и лишних вызовов DOMContentLoaded.

MDN contributors / Технический писатель

Для управления порядком выполнения нескольких скриптов в чистом JavaScript удобно использовать атрибуты defer и async. Скрипты с атрибутом defer загружаются параллельно, но выполняются только после того, как весь документ распарсится, и в том порядке, в котором они находятся в разметке. Это гарантирует, что второй скрипт выполнится после полной загрузки DOM, и событие DOMContentLoaded не будет срабатывать дважды. Если нужно выполнить код сразу после полной загрузки DOM, можно добавить обработчик document.addEventListener('DOMContentLoaded', ...) один раз и внутри него вызвать нужный код, либо использовать window.onload для полной загрузки всех ресурсов. Альтернативой повторной отправки события DOMContentLoaded является использование пользовательского события (например, new CustomEvent('myScriptsLoaded')) и его слушателей, либо просто вызывать функции напрямую после загрузки скриптов. Ниже приведён пример использования defer:

html
<script defer src="first.js"></script>
<script defer src="second.js"></script>
Авторы
MDN contributors / Технический писатель
Технический писатель
Ilya Kantor / Автор, тренер, JS-разработчик
Автор, тренер, JS-разработчик
Источники
MDN Web Docs / Портал документации
Портал документации
javascript.info / Tutorial Portal
Tutorial Portal
Проверено модерацией
НейроОтветы
Модерация