Hujjat va resurs yuklanishi

Skriptlar: async, defer

Zamonaviy saytlarda skriptlar ko'pincha HTML'dan "og'irroq" bo'ladi: ular hajmi katta, yuklanishi va ishga tushishi vaqt oladi. Brauzer HTML'ni o'qib turib <script> tegiga duch kelsa, u DOM qurishni to'xtatib skriptni yuklab bajarishi kerak. Bu esa sahifaning ko'rinishini kechiktiradi. Bu muammoni hal qilish uchun ikkita atribut bor: defer va async. Ushbu darsda ular qanday ishlashini va farqini chuqur o'rganamiz.

Bu darsdagi HTML/DOM misollari brauzer muhitiga tegishli. Kurs sahifasidagi maydoncha Node.js muhitida ishlaydi, shu sabab bu misollar statik โ€” faqat o'qish uchun, ishga tushirib bo'lmaydi.

Bloklovchi skript muammosi

Ikkita muhim muammo bor. Birinchidan, skriptlar undan keyingi DOM elementlarini ko'ra olmaydi. Shuning uchun ularga hodisa biriktira olmaydi:

<p>...skriptdan oldingi kontent...</p>

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

<!-- bu tugmani skript ko'rmaydi -->
<p>...skriptdan keyingi kontent...</p>

Ikkinchidan, agar sahifaning yuqorisida katta skript bo'lsa, u "bloklovchi" โ€” brauzer skriptni yuklab bajarmaguncha, undan pastdagi sahifani ko'rsata olmaydi:

<p>...skriptdan oldingi kontent...</p>

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

<!-- bu skript yuklanmaguncha ko'rinmaydi -->
<p>...skriptdan keyingi kontent...</p>

Bunga qisman yechim bor โ€” skriptni sahifaning eng oxiriga, </body> yopilishidan oldin qo'yish. Unda skript o'zidan yuqoridagi barcha elementlarni ko'radi va sahifa ko'rsatilishini bloklamaydi:

<body>
  ...barcha kontent skriptdan yuqorida...

  <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
</body>

Lekin bu yechim ideal emas: brauzer skriptni faqat butun HTML hujjatni yuklab bo'lgach ko'radi va yuklashni boshlaydi. Katta HTML'larda bu sezilarli kechikish beradi. Bu muammoni defer va async atributlari hal qiladi.

defer

defer atributi brauzerga skriptni "fon rejimida" yuklashni buyuradi: brauzer skriptni yuklab olishda kutmaydi, HTML'ni qurishda davom etadi. Skript esa DOM to'liq tayyor bo'lgach bajariladi:

<p>...skriptdan oldingi kontent...</p>

<script defer src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

<!-- darhol ko'rinadi, skriptni kutmaydi -->
<p>...skriptdan keyingi kontent...</p>

Boshqacha aytganda:

Quyidagi misol buni ko'rsatadi. Kontent darhol ko'rinadi, DOMContentLoaded esa skriptni kutadi:

<p>...skriptdan oldingi kontent...</p>

<script>
  document.addEventListener('DOMContentLoaded', () => alert('DOM tayyor (skriptdan keyin)'));
</script>

<script defer src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>

Bu yerda DOMContentLoaded deferli skript yuklanib bajarilishini kutadi.

defer skriptlar tartibni saqlaydi

Bir necha deferli skript bo'lsa, ular hujjatdagi tartibda bajariladi โ€” xuddi oddiy skriptlar kabi:

<script defer src="long.js"></script>
<script defer src="small.js"></script>

Bu yerda long.js hajmi katta bo'lsa-da, u avval yuklab bo'lingandek kutiladi va small.jsdan oldin bajariladi. Brauzer skriptlarni parallel yuklaydi (tezlik uchun), lekin ularni belgilangan tartibda ishga tushiradi.

defer โ€” kodning to'g'ri tartibda bajarilishi muhim bo'lganda ajoyib. Masalan, ikkinchi skript birinchisiga bog'liq bo'lsa (kutubxona + uni ishlatuvchi kod), defer tartibni kafolatlaydi.
defer atributi faqat tashqi skriptlar uchun, ya'ni src ga ega skriptlar uchun ishlaydi. Agar <script>da src bo'lmasa (kodni to'g'ridan-to'g'ri yozgan bo'lsangiz), defer e'tiborga olinmaydi.

async

async atributi ham skriptni fon rejimida yuklaydi, lekin butunlay boshqacha mustaqillik beradi. asyncli skript to'liq mustaqil:

<p>...skriptdan oldingi kontent...</p>

<script>
  document.addEventListener('DOMContentLoaded', () => alert('DOM tayyor!'));
</script>

<script async src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script async src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>

Bu yerdagi tartib "belgilanmagan":

async boshqa skriptlarga bog'liq bo'lmagan mustaqil skriptlar uchun ideal: hisoblagichlar (counters), reklama, analitika kabi uchinchi tomon skriptlari. Ular sahifangizning boshqa kodiga bog'liq emas va sizning kodingiz ham ularga bog'liq emas.

async va defer farqi

Ikkalasi ham skriptni bloklamasdan fon rejimida yuklaydi. Asosiy farq โ€” tartib va qachon bajarilishi:

Uni jadval ko'rinishida umumlashtiramiz:

                Tartib               DOMContentLoaded
-----------------------------------------------------------
defer   |  hujjatdagi tartibda     |  KUTADI (undan oldin)
async   |  load-first tartibida    |  BOG'LIQ EMAS (kutmaydi)
Amaliyotda defer odatda DOM'ga muhtoj yoki bir-biriga bog'liq skriptlar uchun, async esa mustaqil skriptlar (analitika, reklama) uchun ishlatiladi. Ko'p hollarda dasturchilar bosh sahifa kodini defer bilan, uchinchi tomon vidjetlarini async bilan yuklaydi.
Agar skriptda ham async, ham defer yozilsa, zamonaviy brauzerlarda async ustunlik qiladi. Eski, asyncni qo'llab-quvvatlamaydigan brauzerlarda esa defer zaxira sifatida ishlaydi.

Dinamik skriptlar

Skriptni HTML'da yozish shart emas โ€” uni JavaScript orqali dinamik ravishda ham qo'shishimiz mumkin. Buning uchun element yaratib, uni hujjatga qo'shamiz:

let script = document.createElement('script');
script.src = '/article/dynamic-scripts/long.js';

document.body.append(script); // shu paytda yuklanish va bajarilish boshlanadi

Muhim nozik jihat: dinamik qo'shilgan skriptlar sukut bo'yicha async kabi harakat qiladi. Ya'ni:

Buni o'zgartirib, aniq tartibni tiklash mumkin โ€” script.async = false qo'yish orqali. Unda skriptlar oddiy defer kabi hujjatdagi tartibda bajariladi:

function loadScript(src) {
  let script = document.createElement('script');
  script.src = src;
  script.async = false; // tartibni saqlash uchun
  document.body.append(script);
}

// long.js avval bajariladi, chunki oldin qo'shildi (async=false tufayli)
loadScript('/article/dynamic-scripts/long.js');
loadScript('/article/dynamic-scripts/small.js');

Agar script.async = false qo'ymasak, tartib "load-first" bo'lardi โ€” kichikroq small.js ehtimol oldin yuklanib, oldin bajarilardi.

script.async = false โ€” bir necha dinamik skriptni to'g'ri tartibda yuklashning oddiy usuli. Bu ayniqsa modulli kutubxonalarni tartib bilan yuklashda foydali.

Xulosa

async va defer ikkalasi ham skript yuklanishini bloklamasdan amalga oshiradi va foydalanuvchi sahifani deyarli darhol ko'radi. Farqlar:

Muhim tavsiya: defer yoki async ishlatganda skript yuklanmasdan turib sahifa ko'rsatilishini yodda tuting. Foydalanuvchi skript hali ishga tushmagan sahifani ko'radi โ€” ba'zi tugmalar hali "jonlanmagan" bo'lishi mumkin. Shu sabab yuklanish holatini ko'rsatuvchi indikator qo'shish yoki funksionallikni asta-sekin yoqish yaxshi amaliyot hisoblanadi.