17 лютого 2022 р.

Скрипти: async, defer

На сучасних вебсайтах скрипти часто “важчі” за HTML: їх розмір завантаження більший, і час обробки теж довший.

Коли браузер завантажує HTML і зустрічає тег <script>...</script>, він не може продовжувати будівництво DOM. Він повинен виконати скрипт прямо зараз. Те ж саме відбувається для зовнішніх скриптів <script src="..."></script>: браузер повинен зачекати, щоб скрипт завантажився, виконати завантажений скрипт, і тільки тоді він може обробити решту сторінки.

Це призводить до двох важливих проблем:

  1. Скрипти не можуть бачити елементи DOM під ними, тому вони не можуть додавати обробники тощо.
  2. Якщо у верхній частині сторінки є громіздкий скрипт, він “блокує сторінку”. Користувачі не можуть побачити вміст сторінки, поки він не завантажиться та не запуститься:
<p>...вміст перед скриптом...</p>

<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<!-- Це не видно, поки скрипт завантажується -->
<p>...вміст після скрипту...</p>

Для цього є деякі обхідні шляхи. Наприклад, ми можемо розмістити скрипт внизу сторінки. Тоді він зможе бачити елементи над ним і не блокуватиме показ вмісту сторінки:

<body>
  ...весь вміст знаходиться над скриптом...

  <script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
</body>

Але це рішення далеко не ідеальне. Наприклад, браузер помічає скрипт (і може почати його завантаження) лише після того, як повністю завантажить HTML-документ. Для великих документів HTML це може спричинити помітну затримку.

Такі речі невидимі для людей, які використовують дуже швидкі з’єднання, але багато людей у світі все ще мають низьку швидкість Інтернету та використовують далеко не ідеальне мобільне інтернет-з’єднання.

На щастя, є два атрибути <script>, які вирішують нашу проблему: defer і async.

defer

Атрибут defer повідомляє браузеру, що йому не треба чекати на скрипт. Замість цього браузер продовжить обробляти HTML, будувати DOM. Скрипт завантажується “у фоновому режимі”, а потім запускається, коли DOM повністю побудовано.

Ось той самий приклад, що й вище, але з defer:

<p>...вміст перед скриптом...</p>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<!-- видно відразу -->
<p>...вміст після скрипту...</p>

Іншими словами:

  • Скрипти з defer ніколи не блокують сторінку.
  • Скрипти з defer завжди виконуються, коли DOM готово (але перед подією DOMContentLoaded).

Наступний приклад демонструє другу частину:

<p>...вміст перед скриптами...</p>

<script>
  document.addEventListener('DOMContentLoaded', () => alert("DOM готово після defer!"));
</script>

<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>

<p>...вміст після скриптів...</p>