Defer не загружает страницу быстрее

Я пытаюсь понять отсрочку на простом примере. У меня есть следующий код JS, который занимает 5 секунд для запуска.

home.js

function wait(ms){
    var start = new Date().getTime();
    var end = start;
    while(end < start + ms) {
      end = new Date().getTime();
   }

 }

 wait(5000);

Я использую это в home.html, как показано ниже:

<html>
    <head>
        <script defer src="home.js"></script>
    </head>
<body>
    <h1>Hello World!</h1>
</body>
</html>

Я обнаружил, что если использовать defer или поместить мой скрипт в конец тега body (в моем понимании оба они эквивалентны), hello world появляется только через 5 секунд. Но если я использую asnyc, "Hello World" появляется сразу. Почему это?

4 ответа

Давайте посмотрим на это (откройте консоль отладки, чтобы увидеть вывод):

test.js

document.addEventListener("DOMContentLoaded", function(event) {
    console.log("[4]: 'DOMContentLoaded' event fired!");
});

function wait(ms){
    console.log("[2]: 'wait' started!");
    var start = new Date().getTime();
    var end = start;
    while(end < start + ms) {
        end = new Date().getTime();
    }
    console.log("[3]: 'wait' finished!");
}

wait(5000);

test.html

<html>
    <head>
        <script defer src="test.js"></script>
    </head>
<body>
    <h1>Hello World!</h1>
    <script>console.log('[1]: DOM is parsed!')</script>
</body>
</html>

Консольный журнал:

[1]: DOM анализируется!
[2]: ожидание началось!
[3]: "подождите" закончено!
[4]: событие 'DOMContentLoaded' сработало!

Теперь вы можете увидеть эти шаги:

  1. DOM загружен и проанализирован.
  2. wait функция запускается (потому что это deferred и запустить после анализа DOM, но до DOMContentLoaded событие запущено).
  3. Сценарий 'defer` закончен.
  4. DOMContentLoaded событие происходит (только после всех defer скрипты выполняются в том порядке, в котором они идут в HTML).
  5. HTML отображается, но некоторые ресурсы, такие как таблицы стилей и изображения, все еще могут загружаться.

Время рендеринга DOM может быть различным в разных браузерах и способах размещения HTML-файла: в Chrome (как показали мои тесты) в случае http:// DOM отображается раньше defer скрипты запускаются, но в случае file:/// DOM отображается после defer скрипты запускаются и их синхронный код завершается.

Резюме (из вашего вопроса в комментариях):

  • defer а также async скрипты загружаются асинхронно.
  • defer Скрипты будут выполняться в том порядке, в котором они размещены в HTML.
  • defer сценарии будут выполняться после загрузки и анализа DOM, но до DOMContentLoaded уволен.
  • async Скрипт может быть загружен в любое время и запущен в любом порядке (скорее всего, в порядке их загрузки). Если такой скрипт загружается до анализа DOM, вы не можете ожидать, что этот скрипт сможет найти некоторые элементы в HTML. Кроме того, в этом случае браузер не ожидает загрузки и выполнения скрипта для запуска DOMContentLoaded событие.

@ Сергей ответит хорошо, но на самом деле ничего не объясняет. Принципиальная схема в соответствии с 4.12.1 здесь показывает, что когда defer если браузер ожидает начала выполнения скрипта, пока не будет проанализирован весь HTML. Ключ разбора не рендеринг. Рендеринг начинается одновременно с выполнением отложенного сценария, но в этом конкретном случае сценарий блокируется в тяжелом цикле. Если wait() функция не блокируется, рендеринг имеет возможность эффективно обновлять DOM сразу после завершения анализа и параллельно с неблокирующим отложенным выполнением скрипта. DOMContentLoaded все равно будет запущен, когда скрипт завершит работу.

function wait(ms){
  return new Promise((resolve, reject) => {setTimeout(() => resolve(), ms)});
}

 wait(3000).then(() => console.log('done'));
<html>
    <head>
        <script defer src="home.js"></script>
    </head>
<body>
    <h1>Hello World!</h1>
</body>
</html>

Чтобы ответить на ваш непосредственный вопрос, поможет документация Script Element на MDN.

Перенести:

Скрипты с атрибутом defer будут предотвращать запуск события DOMContentLoaded до тех пор, пока скрипт не загрузится и не завершит оценку.

асинхронный:

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

Асинхронный сценарий не задерживает событие DOMContentLoaded от запуска.

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

Выдержка из DOMContentLoaded:

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

Обычной практикой будет добавление содержимого dom после загрузки документа. Вы все еще можете сделать это с таймаутом, но обычно дополнительный контент DOM будет загружен после некоторого другого асинхронного действия, такого как ответ, полученный от fetch.

Вот пример добавления контента dom после задержки:

function wait(ms){
  window.setTimeout(function() {
    var element = document.createElement("h1")
    element.innerText = "Hello World!"
    document.body.appendChild(element)
  }, ms)
}

 wait(5000);
<html>
    <head>
        <script src="home.js"></script>
    </head>
<body>
    
</body>
</html>

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

Так что вы можете использовать defer или же async в зависимости от ваших требований.

Тем не менее, похоже, что Chrome начинает рисовать страницу до или после выполнения JavaScript в зависимости от того, была ли страница загружена по сети или из файловой системы:

Сеть:

Файловая система:

Хотя я не уверен, почему Chrome выбирает такое поведение, я просто скажу, что это то, что вы не можете контролировать или на что не можете положиться. defer был разработан для оптимизации доставки контента JavaScript по сети, большая часть которого не относится к файловой системе.

Другие вопросы по тегам