Асинхронно загруженные скрипты с обработчиками событий DOMContentLoaded или load не вызываются?

у меня есть скрипт DOMContentLoaded обработчик-

document.addEventListener('DOMContentLoaded', function() {
    console.log('Hi');
});

который я загружаю асинхронно -

<script async src=script.js></script>

однако обработчик событий это не называется. Если я загружаю его синхронно -

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

он работает нормально.

(даже если я изменю DOMContentLoaded событие load событие, оно никогда не вызывается.)

что это дает? Обработчик события должен быть зарегистрирован независимо от того, как скрипт загружен браузер, нет?

редактировать: он не работает на Chrome 18.0.1025.11 beta, но с DOMContentLoaded, это тут в бета-версии Firefox 11 (но с load это не так). Иди разберись.

О ВЕЛИКИЕ ВЛАДЫКИ JAVASCRIPT И DOM, ПОЖАЛУЙСТА, ПОКАЖИТЕ ОШИБКУ МОИХ ПУТЕЙ!

4 ответов


загружая скрипт асинхронно, вы сообщаете браузеру, что он может загружать этот скрипт независимо от других частей страницы. Это означает, что страница может завершить загрузку и может выстрелить DOMContentLoaded перед загрузкой скрипта и перед регистрацией события. Если это произойдет, вы пропустите событие (это уже произошло, когда вы регистрируетесь на него).

в некоторых браузерах, вы можете проверить, чтобы увидеть, если он уже загружен. Я не проверил все совместимость браузера, но в Firefox 3.6+ (MDN doc), вы можете увидеть:

if (document.readyState !== "loading")

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

на самом деле, как ссылочный источник и идея реализации, jQuery делает то же самое с ним .ready() метод и он выглядит широко поддерживается. jQuery имеет этот код после .ready() - Это называется, что сначала проверяет, если документ уже нагруженный. Если это так, он немедленно вызывает функцию ready, а не привязывает прослушиватель событий:

// Catch cases where $(document).ready() is called after the
// browser event has already occurred.
if ( document.readyState === "complete" ) {
    // Handle it asynchronously to allow scripts the opportunity to delay ready
    return setTimeout( jQuery.ready, 1 );
}

Это не окончательный ответ, но заставил меня понять, почему неправильно использовать async со скриптом, который нужно изменить DOM, поэтому нужно дождаться события DOMContentLoaded. Надежда может быть полезной.

enter image description here

(источник: запуск кода в нужное время из kirupa.com)


один из способов обойти это-использовать событие load для объекта window.

это произойдет позже, чем DOMContentLoaded, но, по крайней мере, вам не придется беспокоиться о пропущенном событии.

window.addEventListener("load", function () {
   console.log('window loaded');
});

Если вам действительно нужно поймать событие DOMContentLoaded, вы можете использовать объект Promise. Обещание разрешится, даже если это произошло раньше:

HTMLDocument.prototype.ready = new Promise(function (resolve) {
if (document.readyState != "loading")
    return resolve();
else
    document.addEventListener("DOMContentLoaded", function () {
        return resolve();
    });
});

document.ready.then(function () {
    console.log("document ready");
});

имитация медленного подключения 3G используя Chrome devtools у меня были проблемы с манипулированием DOM из внешнего async сценарий, однако этот фрагмент, который я нашел где-то на GitHub решил это за меня:

var DOMReady = function(callback) {document.readyState === "interactive" || document.readyState === "complete" ? callback() : document.addEventListener("DOMContentLoaded", callback);};
DOMReady(function() {
  //DOM is ready!
});

может ли кто-нибудь поручиться за это?
как это соотносится с уже предложенных решений?


Я также нашел аналогичное решение здесь:
https://javascript.info/onload-ondomcontentloaded#readystate