IlmHamroh
JavaScript Full-stack/9-qism. JavaScript brauzerda va debugging21/29-dars17 daqiqa
Mundarija (33)

Sahifa hayot sikli JavaScript'da: visibilitychange, pagehide, beforeunload va bfcache

Qisqacha: Sahifa yuklanadi (DOMContentLoaded, load), ko'rinadi va yashirinadi (visibilitychange, document.visibilityState), keyin tark etiladi (pagehide). Ma'lumotni saqlash va ishni to'xtatish uchun eng ishonchli signal — visibilitychange da "hidden" holati: telefonda boshqa ilovaga o'tilganda boshqa hodisa kelmasligi mumkin. "Orqaga" bilan qaytishni pageshow va event.persisted bildiradi; eskirgan unload esa shu tez qaytishni (bfcache) buzadi.

Bu darsda

  • Sahifaning yuklanishdan yopilishgacha bo'lgan bosqichlarini va har birining hodisasini bilasiz.
  • visibilitychange bilan tab yashirilganda ishni to'xtatib, ma'lumotni saqlay olasiz.
  • beforeunload ni faqat kerak bo'lganda va to'g'ri ishlatasiz.
  • bfcache nima ekanini, pagehide/pageshow va persisted ni tushunasiz va unload dan voz kechasiz.
  • Portfolio menyusi "orqaga" qaytilganda ochiq qolib ketmaydigan bo'ladi.

Oldin bilishingiz kerak: Brauzer muhiti: window, document va skript yuklanishi, Listener opsiyalari: once, passive, signal, Custom events va EventTarget, Fokus, scroll va resize hodisalari.

1. Nega bu kerak?

Bir mehmon telefonda «Bahor» saytidan stol band qilyapti. Formaning yarmini to'ldirgan paytda unga qo'ng'iroq keldi. Gaplashib bo'lib brauzerga qaytsa — forma bo'm-bo'sh. Telefon xotirani tejash uchun yashirin tabni "uxlatib", keyin qaytadan yuklagan edi.

Aziz esa saytga "Choy damlanmoqda: 5:00" degan taymer qo'ydi. U boshqa tabga o'tib, ikki daqiqadan keyin qaytdi — taymer 4:30 da turibdi. Brauzer yashirin tabdagi taymerlarni sekinlashtirgan.

Uchinchi holat. Mehmon telefonda menyuni ochdi, "Loyihalar" ga bosdi, keyin "Orqaga" tugmasini bosdi. Sahifa bir zumda qaytdi — lekin menyu ochiq holida.

Uchala holatning sababi bitta: sahifa hayoti faqat "ochildi" va "yopildi" dan iborat emas. U yashirinadi, muzlatiladi, xotirada saqlanadi va qaytadi. Brauzer har bosqich haqida signal beradi. Bu dars — shu signallar xaritasi.

2. Hayot sikli: umumiy ko'rinish

Sahifaning asosiy holatlari diagrammada. Strelkalar ustida — o'tishda keladigan hodisa:

stateDiagram-v2
  [*] --> Yuklanmoqda
  Yuklanmoqda --> Korinadi: DOMContentLoaded, load, pageshow
  Korinadi --> Yashirin: visibilitychange (hidden)
  Yashirin --> Korinadi: visibilitychange (visible)
  Yashirin --> Ketdi: pagehide
  Ketdi --> bfcache: sahifa xotirada saqlandi
  Ketdi --> [*]: sahifa yo'q qilindi
  bfcache --> Korinadi: pageshow (persisted true)

Diagrammani yuqoridan o'qing. Uch narsaga e'tibor bering:

  • Sahifa yopilishidan oldin deyarli doim Yashirin holatidan o'tadi. Mehmon tabni yopganda ham, boshqa sahifaga o'tganda ham avval visibilitychange keladi.
  • "Ketdi" — oxiri emas. Sahifa bfcache da saqlanib, "Orqaga" bosilganda qaytishi mumkin.
  • Telefonda tizim yashirin sahifani ogohlantirmasdan yo'q qilishi mumkin. Shuning uchun Yashirin — ishonchli "oxirgi so'z" aytish mumkin bo'lgan oxirgi joy.

Endi har bosqichni alohida ko'ramiz.

3. Yuklanish bosqichi

3.1 To'liq tartib

Brauzer muhiti darsida DOMContentLoaded, load va document.readyState ni ko'rgansiz. Endi ularga yana ikki hodisani qo'shamiz va hammasini bitta konsolda ko'ramiz:

html
<!DOCTYPE html>
<html lang="uz">
<head>
  <meta charset="utf-8">
  <title>Hayot sikli</title>
  <script>
    console.log("skript:", document.readyState);
    document.addEventListener("readystatechange", () => {
      console.log("readystatechange:", document.readyState);
    });
    document.addEventListener("DOMContentLoaded", () => {
      console.log("DOMContentLoaded");
    });
    window.addEventListener("load", () => {
      console.log("load");
    });
    window.addEventListener("pageshow", (event) => {
      console.log("pageshow, persisted:", event.persisted);
    });
  </script>
</head>
<body>
  <h1>«Bahor» menyusi</h1>
</body>
</html>

Konsolda:

text
skript: loading
readystatechange: interactive
DOMContentLoaded
readystatechange: complete
load
pageshow, persisted: false

Ikki yangi hodisa:

  • readystatechange — document.readyState har o'zgarganda keladi. interactive bo'lganda u DOMContentLoaded dan oldin, complete bo'lganda — load dan oldin keladi. Kamdan-kam ishlatiladi, lekin eski kodda uchraydi.
  • pageshow — sahifa ekranga chiqqanda, eng oxirida keladi. persisted: false — "sahifa yangidan yuklandi". Bu xususiyat nega kerakligini bfcache bo'limida ko'ramiz.

3.2 Qaysi birini tanlash kerak?

Brauzer muhiti darsidagi qoida o'zgarmaydi: kod defer li faylda bo'lsa, hech qanday o'ram kerak emas. Rasm o'lchami kabi to'liq yuklangan narsa kerak bo'lsagina — load. "Sahifa har safar ko'rsatilganda" kerak bo'lgan ish uchun esa — pageshow, chunki u "Orqaga" bilan qaytganda ham keladi.

4. Ko'rinish: Page Visibility API

4.1 visibilitychange va visibilityState

Page Visibility API ("sahifa ko'rinishi" API'si) — "mehmon sahifani hozir ko'ryaptimi?" degan savolga javob beradigan kichik Web API. Eslatma: Web API — brauzer JavaScript'ga beradigan tayyor imkoniyatlar (Brauzer muhiti). Bu API'ning ikki qismi bor:

  • document.visibilityState — "visible" (ko'rinadi) yoki "hidden" (yashirin). Eski document.hidden ham bor — u shunchaki true/false.
  • visibilitychange hodisasi — holat o'zgarganda document ga keladi.

Sahifa qachon "yashirin" bo'ladi? Mehmon boshqa tabga o'tganda, brauzer oynasini yig'ganda, telefonda boshqa ilovaga o'tganda yoki ekranni o'chirganda. Tabni yopganda va boshqa sahifaga o'tganda ham — oxirgi marta.

html
<p>Boshqa tabga o'ting va bir necha soniyadan keyin qayting.</p>
<p id="holat">Siz shu yerdasiz.</p>
<script>
  const holat = document.getElementById("holat");
  let ketganVaqt = 0;

  document.addEventListener("visibilitychange", () => {
    console.log(`visibilitychange: ${document.visibilityState}`);
    if (document.visibilityState === "hidden") {
      ketganVaqt = Date.now();
    } else {
      const soniya = Math.round((Date.now() - ketganVaqt) / 1000);
      holat.textContent = `Qaytdingiz! ${soniya} soniya o'tdi.`;
    }
  });
</script>

Boshqa tabga o'tib qayting. Konsolda ikki qator chiqadi: visibilitychange: hidden va visibilitychange: visible. Sahifada esa necha soniya yo'q bo'lganingiz yoziladi. Biz Chrome'da tablarni almashtirib tekshirdik — jonli oyna ichida ham hodisa keladi, chunki u siz ko'rayotgan tab bilan birga yashirinadi.

Date.now() — hozirgi vaqt, millisekundlarda (1970-yildan beri o'tgan). Ikki vaqtning ayirmasi — o'tgan millisekundlar, 1000 ga bo'lsak — soniyalar.

4.2 Yashirin tabda brauzer nima qiladi

Brauzer yashirin tabni "tejash rejimi"ga o'tkazadi — Aziz taymerining sababi shu. Chrome hujjatlariga ko'ra:

  • yashirin tabda setTimeout taymerlari ko'pi bilan soniyada bir marta ishlaydi;
  • tab 5 daqiqadan ko'proq yashirin tursa va taymerlar zanjir bo'lib bir-birini chaqirsa — daqiqada bir marta gacha sekinlashadi;
  • animatsiya kadrlari (requestAnimationFrame) umuman to'xtaydi.

Xulosa: yashirin tabda "har soniya" ishlaydigan kodga ishonmang. Taymerni sanab emas, vaqtni o'lchab hisoblang (Date.now() ayirmasi, xuddi yuqoridagi misoldagidek). Yoki yashirinish bilan to'xtatib, ko'rinish bilan davom ettiring.

4.3 visibilitychange da nima qilinadi

Vazifa hidden bo'lganda visible bo'lganda
Video, musiqa, animatsiya Pauza Davom (yoki mehmon o'zi bosadi)
Har necha soniyada yangilanadigan ma'lumot To'xtatish Darhol yangilab, davom ettirish
Yozilgan qoralama Saqlash —
Analitika ("sahifada qancha turdi") Yuborish Yangi hisob boshlash

Saqlash va yuborishni aynan hidden ga qo'yish — eng muhim qoida. Telefonda mehmon boshqa ilovaga o'tsa, keyin tizim brauzerni yopib yuborsa, sahifa boshqa hech qanday signal olmaydi: pagehide ham, beforeunload ham kelmaydi. visibilitychange esa keladi.

Saqlash uchun localStorage kerak — uni keyingi qismda o'rganamiz. Analitikani serverga yuborish uchun fetch ning keepalive opsiyasi yoki navigator.sendBeacon bor: ular sahifa yopilayotgan bo'lsa ham so'rovni oxiriga yetkazadi. fetch ni fetch asoslari darsida ko'ramiz — hozir bilish shart emas.

Baseline ma'lumotiga ko'ra visibilitychange hodisasi 2021-yil apreldan beri barcha asosiy brauzerlarda (keng tarqalgan Baseline).

Tekshirib ko'ring: Mehmon telefonda bron formasini to'ldirib turib, Telegram'ga o'tdi. Bir soatdan keyin tizim brauzerni xotiradan chiqarib yubordi. Qaysi hodisada qoralamani saqlash kerak edi?

Javob

visibilitychange da, document.visibilityState === "hidden" bo'lganda. Telegram'ga o'tilgan paytda sahifa yashirin bo'ldi — bu oxirgi ishonchli signal. Tizim brauzerni keyin, ogohlantirmasdan yopdi: pagehide, beforeunload kelmadi.

5. Ketish bosqichi

5.1 beforeunload: "Rostdan chiqasizmi?"

Mehmon forma to'ldirib bo'lmasdan tabni yopmoqchi bo'lsa, brauzer "Saytdan chiqasizmi? Kiritgan o'zgarishlaringiz saqlanmasligi mumkin" deb so'rashi mumkin. Buni beforeunload hodisasi qiladi:

js
const forma = document.getElementById("bron-forma");

function ogohlantir(event) {
  event.preventDefault();
}

// Forma o'zgardi — endi ogohlantirish kerak
forma.addEventListener("input", () => {
  window.addEventListener("beforeunload", ogohlantir);
});

// Forma yuborildi — ogohlantirish endi kerak emas
forma.addEventListener("submit", () => {
  window.removeEventListener("beforeunload", ogohlantir);
});

Bu kodni jonli oynada sinab bo'lmaydi: oyna boshqa sahifaga o'ta olmaydi va dialog oynalarini ko'rsatmaydi. O'z faylingizda sinang: forma maydoniga bir harf yozing va tabni yopishga harakat qiling.

Qoidalar:

  • Faqat haqiqatan saqlanmagan narsa bo'lganda. Tinglovchini mehmon biror narsa yozgandan keyin qo'shing va saqlangach olib tashlang (removeEventListener — bir xil funksiya havolasi bilan). Har sahifada doim turgan beforeunload mehmonni bezovta qiladi.
  • Xabar matnini o'zgartirib bo'lmaydi. Brauzer o'zining umumiy matnini ko'rsatadi. Eski kodda return "Rostdan ketasizmi?" uchraydi — matn baribir ko'rinmaydi. Eski brauzerlar uchun event.returnValue = true ham yozib qo'yishadi.
  • Mehmon sahifa bilan ishlamagan bo'lsa, dialog chiqmaydi. Brauzer faqat mehmon kamida bir marta bosgan yoki yozgan sahifada so'raydi — reklama sahifalari mehmonni "ushlab qolmasligi" uchun.
  • Ishonchsiz. Telefonda u ko'pincha umuman kelmaydi. Ma'lumotni saqlashni unga ishonib qo'ymang — buni visibilitychange qiladi.

5.2 unload — eskirgan, ishlatmang

Eski kodda sahifa yopilganda ishlaydigan unload hodisasini ko'p uchratasiz. Bugun u ishlatilmaydi, uchta sababga ko'ra:

  • telefonda u ko'pincha kelmaydi (xuddi beforeunload kabi);
  • u sahifani bfcache'ga tushishdan to'xtatadi — "Orqaga" tugmasi sekinlashadi (pastda o'zimiz tekshiramiz);
  • Chrome jamoasi uni bosqichma-bosqich o'chirish rejasini e'lon qilgan.

O'rniga — pagehide (sahifadan ketildi) yoki visibilitychange.

Tekshirib ko'ring: Aziz bron formasining qoralamasini beforeunload ichida saqlayapti. Kompyuterda hammasi joyida. Telefonda nima bo'lishi mumkin?

Javob

Qoralama yo'qolishi mumkin. Telefonda mehmon boshqa ilovaga o'tadi, keyin tizim brauzerni ogohlantirmasdan yopadi — beforeunload umuman kelmaydi. Saqlash visibilitychange ga, hidden holatiga qo'yiladi: bu hodisa telefonda ham keladi.

6. bfcache: "Orqaga" tugmasining sirli tezligi

6.1 bfcache nima?

bfcache (back/forward cache, orqaga/oldinga keshi) — brauzer tark etilgan sahifani bir muddat xotirada "muzlatib" saqlaydi. Mehmon "Orqaga" bossa, sahifa qaytadan yuklanmaydi — xotiradan bir zumda qaytariladi. Hamma narsa joyida: aylantirilgan joy, formaga yozilgan matn, JavaScript o'zgaruvchilari, ochiq menyu ham.

Hayotdan o'xshatish: kitobga xatcho'p qo'yib, uni stolga qo'yasiz. Qaytib kelganingizda kitobni boshidan o'qimaysiz — xatcho'p turgan sahifadan davom etasiz. bfcache — sahifa uchun xatcho'p.

Bu tezlik uchun juda katta yutuq. Lekin u ikki savolni tug'diradi: "sahifa bfcache'ga tushdimi?" va "sahifa yangidan yuklandimi yoki xotiradan qaytdimi?"

6.2 pagehide, pageshow va persisted

Ikkala savolga ikki hodisa javob beradi:

  • pagehide — sahifadan ketildi. event.persisted — true bo'lsa, sahifa bfcache'ga saqlanyapti.
  • pageshow — sahifa ko'rsatildi. event.persisted — true bo'lsa, sahifa bfcache'dan qaytdi (yangidan yuklanmadi).

Biz Chrome 154 da ikki sahifali kichik saytda tekshirdik: A sahifasi → B ga havola → "Orqaga":

A sahifasida Ketishda (pagehide) Qaytishda (pageshow)
unload tinglovchisi yo'q persisted: true persisted: true — xotiradan
unload tinglovchisi bor persisted: false persisted: false — qaytadan yuklandi

Bitta unload tinglovchisi sahifani bfcache'dan mahrum qildi.

Sahifa xotiradan qaytganda DOMContentLoaded va load qayta kelmaydi — sahifa allaqachon yuklangan. Faqat pageshow keladi. Shuning uchun "sahifa har safar ko'rsatilganda" qilinadigan ish (masalan, savatdagi sonni yangilash yoki ochiq qolgan menyuni yopish) pageshow ga yoziladi:

js
window.addEventListener("pageshow", (event) => {
  if (event.persisted) {
    console.log("Sahifa bfcache'dan qaytdi");
    savatSoniniYangila();
  }
});

Bu kodni ham jonli oynada sinab bo'lmaydi — u boshqa sahifaga o'tmaydi. O'z saytingizda sinang: havolani bosing, keyin "Orqaga".

6.3 DevTools: sahifa bfcache'ga tushadimi?

Chrome'da buni tekshirishning tayyor usuli bor:

  1. DevTools → Application paneli → chapda Back/forward cache.
  2. Test back/forward cache tugmasini bosing. DevTools sahifani boshqa manzilga o'tkazib, darhol qaytaradi.
  3. Natija: "Successfully served from back/forward cache" yoki to'sqinlik qilgan sabablar ro'yxati.

Eng ko'p uchraydigan to'siqlar: unload tinglovchisi (eng keng tarqalgani), sahifadagi ochiq doimiy ulanishlar (masalan, WebSocket — kursda keyinroq) va server yuborgan Cache-Control: no-store sarlavhasi (ko'p holatlarda). Panel aniq sababni va uni tuzatish mumkinmi — shuni yozadi.

Tekshirib ko'ring: Savat sahifasida "Savatda: 3 ta" yozuvi bor. Mehmon taom sahifasiga o'tib, yana bitta qo'shdi (endi 4 ta), keyin "Orqaga" bosdi. Savat sahifasi bfcache'dan qaytdi. Ekranda nechta yozilgan va qaysi hodisa buni tuzatadi?

Javob

"3 ta" — eski holat. bfcache sahifani tark etilgan paytdagidek qaytaradi, DOMContentLoaded qayta ishlamaydi. Tuzatish — pageshow da event.persisted true bo'lsa, sonni yangilash.

7. Page Lifecycle API: muzlatish va qayta tiklash

Chrome bundan ham chuqurroq signallar beradi. Uzoq vaqt yashirin turgan tabni Chrome muzlatishi (freeze) mumkin: taymerlar va boshqa ishlar butunlay to'xtaydi. Xotira yetmasa — tabni o'chirib tashlashi (discard) mumkin: mehmon qaytganda sahifa qaytadan yuklanadi.

  • freeze va resume hodisalari (document da) — muzlatishdan oldin va keyin. Biz ularni Chrome'da sahifani sun'iy muzlatib tekshirdik — hodisalar shu tartibda keldi: avval visibilitychange: hidden, keyin freeze, resume.
  • document.wasDiscarded — true bo'lsa, sahifa yashirin paytda o'chirib tashlangan va hozir qayta yuklangan. «Nega bu kerak?» bo'limidagi mehmonning formasi aynan shunday yo'qolgan edi.

Bu API faqat Chrome va Edge'da bor (Baseline emas). Shuning uchun asosiy mantiq baribir visibilitychange va pageshow da bo'lsin. freeze va wasDiscarded — qo'shimcha.

js
if (document.wasDiscarded) {
  console.log("Tab o'chirilgan edi — qoralamani tiklaymiz");
}

8. Qaysi hodisada nima qilinadi: xulosa jadvali

Maqsad Hodisa
DOM tayyor bo'lganda boshlash defer (yoki DOMContentLoaded)
Rasmlar ham kerak load
Ma'lumotni saqlash, analitika yuborish visibilitychange → hidden
Video, taymer, yangilanishni to'xtatish visibilitychange → hidden
Saqlanmagan o'zgarish haqida so'rash beforeunload (faqat kerak bo'lganda)
Sahifadan ketishda tozalash pagehide
"Orqaga" bilan qaytganda holatni yangilash pageshow + persisted
Sahifa yopilganda unload — ishlatmang

O'zingiz to'ldiring:

  • Mehmon Telegram'ga o'tdi. Qoralamani saqlash uchun document.visibilityState qiymati —
  • Sahifa "Orqaga" bilan xotiradan qaytganini bildiradigan pageshow xususiyati — event.
  • Bu xususiyat true bo'lsa, qayta kelmaydigan yuklanish hodisasi —

9. Ko'p uchraydigan xatolar

9.1 Ma'lumotni unload yoki beforeunload da saqlash

Kompyuterda ishlaydi, telefonda esa ma'lumot yo'qoladi: bu hodisalar kelmasligi mumkin. Tuzatish: visibilitychange va document.visibilityState === "hidden".

9.2 Doim yoqilgan beforeunload

Har safar "Rostdan chiqasizmi?" — mehmon g'ashi keladi va keyingi safar saytga qaytishni istamaydi. Tuzatish: tinglovchini faqat saqlanmagan o'zgarish paydo bo'lganda qo'shing va saqlangach olib tashlang.

9.3 bfcache'dan qaytishni hisobga olmaslik

"Orqaga" bosilganda eski savat, ochiq menyu, tugagan taymer ko'rinadi — chunki DOMContentLoaded dagi "tayyorlov" kodi qayta ishlamadi. Tuzatish: pageshow da event.persisted ni tekshiring.

9.4 Yashirin tabda taymerga ishonish

setTimeout bilan "har soniya" sanaydigan taymer yashirin tabda orqada qoladi. Tuzatish: vaqtni Date.now() ayirmasi bilan hisoblang yoki hidden da to'xtatib, visible da davom ettiring.

9.5 document.hidden va fokusni chalkashtirish

"Oyna fokusda emas" (window da blur) — "sahifa ko'rinmaydi" degani emas: ikki oyna yonma-yon tursa, sahifa ko'rinadi, lekin fokusda emas. Tuzatish: ko'rinish uchun — visibilityState, fokus uchun — focus/blur (Fokus hodisalari).

10. Mashqlar

1-mashq (oson): Necha marta chiqib ketdi?

Sahifada "Siz 0 marta boshqa tabga o'tdingiz" yozuvi bo'lsin. Har safar sahifa yashirinsa, son bittaga oshsin va konsolga Yashirindi: 1-marta chiqsin.

html
<p>Siz <output id="soni">0</output> marta boshqa tabga o'tdingiz.</p>
<script>
  // shu yerga yozing
</script>
Yechim
html
<p>Siz <output id="soni">0</output> marta boshqa tabga o'tdingiz.</p>
<script>
  const soni = document.getElementById("soni");
  let marta = 0;
  document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "hidden") {
      marta++;
      soni.textContent = marta;
      console.log(`Yashirindi: ${marta}-marta`);
    }
  });
</script>

Ikki marta boshqa tabga o'tib qaytsangiz: Yashirindi: 1-marta, Yashirindi: 2-marta. visible ga qaytishda hech narsa qilinmaydi — shart faqat hidden ni tekshiradi.

2-mashq (o'rta): Yashirinda to'xtaydigan taymer

"Choy damlanmoqda" taymeri 10 dan 0 gacha sanaydi. Sahifa yashirin bo'lganda sanash to'xtasin va konsolga Pauza: 7 chiqsin. Qaytganda davom etsin: Davom: 7. Nolga yetganda — Choy tayyor!.

Ishora: har soniyada bitta qadam — setTimeout funksiyani 1000 ms dan keyin chaqiradi, funksiya oxirida esa o'zini yana rejalashtiradi (Callback). To'xtatish uchun clearTimeout (Fokus, scroll va resize hodisalari darsidagi debounce'da ko'rgansiz).

Yechim
html
<p>Choy damlanmoqda: <output id="qoldi">10</output> soniya</p>
<script>
  const qoldi = document.getElementById("qoldi");
  let soniya = 10;
  let taymer;

  function qadam() {
    soniya--;
    qoldi.textContent = soniya;
    if (soniya === 0) {
      console.log("Choy tayyor!");
      return;
    }
    taymer = setTimeout(qadam, 1000);
  }

  document.addEventListener("visibilitychange", () => {
    if (soniya === 0) {
      return;
    }
    if (document.visibilityState === "hidden") {
      clearTimeout(taymer);
      console.log(`Pauza: ${soniya}`);
    } else {
      console.log(`Davom: ${soniya}`);
      taymer = setTimeout(qadam, 1000);
    }
  });

  taymer = setTimeout(qadam, 1000);
</script>

Sanash boshlanganda boshqa tabga o'ting va 20 soniya kutib qayting: taymer siz ketgan joydan davom etadi. if (soniya === 0) return; — taymer tugab bo'lgan bo'lsa, yashirinish va qaytish hech narsa qilmasin.

Bu "to'xtatib turish" usuli. Ikkinchi usul — «Yashirin tabda brauzer nima qiladi» bo'limidagi: haqiqiy vaqtni Date.now() bilan o'lchash. Choy taymeri uchun aslida ikkinchisi to'g'riroq: siz boshqa tabda bo'lsangiz ham, choy damlanishda davom etadi!

3-mashq (qiyin): Saqlanmagan izoh

Izoh maydoni bor. Talablar:

  1. Maydonga biror narsa yozilsa, beforeunload ogohlantirishi yoqilsin; "Saqlash" bosilganda o'chirilsin.
  2. Sahifa yashirinsa (hidden) va izoh saqlanmagan bo'lsa, konsolga Qoralama: <matn> chiqsin — bu yerda keyinroq localStorage ga yozamiz.
  3. Saqlangandan keyin yashirinishda hech narsa chiqmasin.

Ishora: holatni let saqlanmagan = false; bilan kuzating. Yozishni sezish — maydonning input hodisasi (input va change darsida batafsil; hozircha "har harf yozilganda keladi" deb biling). beforeunload ni jonli oynada sinab bo'lmaydi — u qismni o'z faylingizda sinang.

Yechim
html
<label for="izoh">Izoh</label>
<textarea id="izoh" rows="2"></textarea>
<button type="button" id="saqlash">Saqlash</button>
<script>
  const izoh = document.getElementById("izoh");
  let saqlanmagan = false;

  function ogohlantir(event) {
    event.preventDefault();
  }

  izoh.addEventListener("input", () => {
    if (!saqlanmagan) {
      saqlanmagan = true;
      window.addEventListener("beforeunload", ogohlantir);
    }
  });

  document.getElementById("saqlash").addEventListener("click", () => {
    saqlanmagan = false;
    window.removeEventListener("beforeunload", ogohlantir);
    console.log("Saqlandi");
  });

  document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "hidden" && saqlanmagan) {
      console.log(`Qoralama: ${izoh.value}`);
    }
  });
</script>

"Osh zo'r" deb yozib boshqa tabga o'tsangiz — konsolda Qoralama: Osh zo'r. "Saqlash" ni bosib, yana tabni almashtirsangiz — faqat Saqlandi, qoralama qatori chiqmaydi.

if (!saqlanmagan) nega kerak? input har harfda keladi. Shartsiz yozsak, addEventListener har harfda chaqirilardi. Bir xil funksiya ikki marta qo'shilmaydi, shuning uchun xato bo'lmasdi — lekin shart niyatni aniq ko'rsatadi: "birinchi o'zgarishda yoq".

4-mashq: Portfolio qadami — "Orqaga" qaytganda menyu yopiq bo'lsin

addEventListener asoslari darsida muammoni ko'rgandik: telefonda menyudan havolani bosib, "Orqaga" qaytilsa, sahifa bfcache'dan menyu ochiq holda tiklanardi. O'shanda har havolaga (keyin delegation bilan) hidePopover() qo'ydik. Lekin bitta yo'l ochiq qoldi: mehmon menyuni ochadi va havolani bosmay, brauzerning "Orqaga" tugmasi yoki ekran chetidan surish bilan ketadi. Sahifa yana ochiq menyu bilan xotiraga tushadi.

  1. window ga pagehide tinglovchisi qo'shing: menyu ochiq bo'lsa (:popover-open), uni hidePopover() bilan yoping.
  2. DevTools → Application → Back/forward cache bilan portfolio sahifasi bfcache'ga tushishini tekshiring. "Successfully served" chiqmasa, sababini o'qing.
Yechim

asosiy.js da, menyu kodi yonida:

js
// Sahifadan qanday ketilmasin — bfcache'ga menyu yopiq tushsin
window.addEventListener("pagehide", () => {
  const menyu = document.getElementById("asosiy-menyu");
  if (menyu?.matches(":popover-open")) {
    menyu.hidePopover();
  }
});

Biz buni Chrome 154 da kichik sinov saytida tekshirdik: menyu ochiq holda boshqa sahifaga o'tib, "Orqaga" qaytdik. pagehide tinglovchisisiz sahifa bfcache'dan (persisted: true) ochiq menyu bilan qaytdi. Tinglovchi bilan — yana persisted: true, lekin menyu yopiq.

Nega pageshow emas, pagehide? Ikkalasi ham ishlaydi. Lekin pagehide da yopsak, bfcache sahifani allaqachon yopiq menyu bilan "suratga oladi". Qaytishda mehmon bir lahza ham ochiq menyuni ko'rmaydi. pageshow da yopsak, qaytishda bir zumga ochiq menyu ko'rinib, keyin yopilishi mumkin.

Havolalardagi eski hidePopover() endi ortiqchami? Boshqa sahifaga olib boradigan havolalar uchun — ha, pagehide ularni ham qamraydi. Lekin #bo'lim kabi shu sahifa ichidagi havola sahifadan chiqmaydi va pagehide bermaydi. Ikkalasini qoldirish xavfsiz.

Application panelidagi testda portfolio "Successfully served from back/forward cache" bo'lishi kerak: unda unload yo'q, GitHub Pages esa no-store yubormaydi. To'siq ko'rsatilsa — sababini yozib qo'ying.

Bu kodni jonli oynada sinab bo'lmaydi: u sahifadan chiqishni talab qiladi. Telefonda yoki Ctrl+Shift+M qurilma rejimida sinang. Commit: JS: sahifadan ketishda (bfcache) mobil menyuni yop.

11. Real ishda

  • Analitika — "sahifada qancha vaqt turdi" degan ma'lumotni yuborishning tavsiya etilgan joyi (web.dev) — visibilitychange (hidden). unload ga tayangan eski skriptlar telefondagi tashriflarning bir qismini yo'qotadi.
  • Video, musiqa, jonli lentalar — yashirin tabda pauza qilish yoki yangilanishni to'xtatish: batareya va internet tejaladi.
  • Onlayn tahrirlagichlar va formalar — qoralamani yashirinishda saqlaydi, beforeunload ni esa faqat saqlanmagan o'zgarish bo'lganda yoqadi.
  • Tezlik — Lighthouse hisobotida bfcache'ga to'sqinlik qilgan sabablar haqida alohida band bor. Uni Lighthouse va PageSpeed darsidagi audit kabi o'qiysiz. Sahifani bfcache'ga moslash — eng arzon tezlashtirishlardan biri.
  • Intervyuda: "DOMContentLoaded va load farqi?", "Nega unload ishlatilmaydi?", "bfcache nima?", "Mobil qurilmada ma'lumotni qaysi hodisada saqlaysiz?" — tez-tez so'raladi.

Xulosa

  • Yuklanish: readystatechange → DOMContentLoaded → load → pageshow.
  • visibilitychange + document.visibilityState — "mehmon ko'ryaptimi?"; hidden — saqlash va to'xtatish uchun eng ishonchli signal, ayniqsa telefonda.
  • Yashirin tabda taymerlar sekinlashadi: vaqtni o'lchang yoki to'xtatib turing.
  • beforeunload — faqat saqlanmagan o'zgarish bo'lganda, preventDefault bilan; matnini o'zgartirib bo'lmaydi.
  • bfcache sahifani xotirada saqlaydi; pagehide/pageshow va event.persisted uni bildiradi; unload bfcache'ni buzadi — ishlatmang.
  • Page Lifecycle (freeze, resume, wasDiscarded) — faqat Chrome/Edge, qo'shimcha sifatida.

Keyingi dars: Forma qiymatlarini o'qish va submit — hodisalar bilan tanishib chiqdik; endi ularni formalarga qo'llaymiz: maydon qiymatini o'qish va yuborishni ushlash.

Manbalar

  • MDN: "Page Visibility API", "Document: visibilitychange event" — developer.mozilla.org
  • MDN: "Window: pagehide event", "Window: pageshow event", "Window: beforeunload event" — developer.mozilla.org
  • web.dev: "Back/forward cache", "Page Lifecycle API" — web.dev
  • Chrome for Developers: "Deprecating the unload event", "Heavy throttling of chained JS timers beginning in Chrome 88" — developer.chrome.com
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Sahifa hayot sikli JavaScript'da: visibilitychange, pagehide, beforeunload va bfcache — IlmHamroh