IlmHamroh
JavaScript Full-stack/11-qism. Asinxron JavaScript va tarmoq30/32-dars18 daqiqa
Mundarija (33)

location va History API: oddiy SPA router yozish

Qisqacha: location — joriy sahifa manzili: location.assign(url) yangi sahifani yuklaydi, replace tarixda iz qoldirmaydi, reload qayta yuklaydi. history.pushState(holat, "", "/menyu") esa manzil satrini sahifani yuklamasdan o'zgartiradi va tarixga yozuv qo'shadi. «Orqaga» bosilganda brauzer popstate hodisasini chiqaradi — siz sahifani location.pathname ga qarab qayta chizasiz. Bitta sahifali ilovalar (SPA) va ularning "router"lari aynan shu ikki qismga qurilgan.

Bu darsda

  • location ning xususiyatlari va assign/replace/reload farqini bilasiz.
  • history.pushState, replaceState, back/forward va popstate bilan brauzer tarixini boshqarasiz.
  • Havolalarni ushlab, sahifani qayta yuklamasdan almashtiradigan oddiy router yozasiz.
  • Hash routing va zamonaviy Navigation API'ni taqqoslay olasiz.
  • Serverda nega "hamma yo'l — index.html" sozlamasi kerakligini tushuntira olasiz.

Oldin bilishingiz kerak: URL va URLSearchParams, Event delegation, Event obyekti va standart harakatlar, Dinamik UI'da qulaylik (a11y).

1. Nega bu kerak?

Oddiy saytda «Menyu» havolasini bossangiz, brauzer serverdan butun yangi sahifani so'raydi: HTML, CSS, skriptlar qayta yuklanadi, ekran bir lahza oqarib, qaytadan chiziladi. JavaScript o'zgaruvchilari ham — sahifa bilan birga — yo'qoladi.

Gmail, Telegram Web yoki YouTube'da esa bunday emas. Bo'limdan bo'limga o'tasiz, ekran oqarmaydi, video o'ynashda davom etadi. Lekin manzil satri baribir o'zgaradi va «Orqaga» tugmasi ishlaydi. Bunday sayt SPA (Single Page Application — bitta sahifali ilova) deyiladi: brauzer bitta HTML sahifani yuklaydi, keyin JavaScript kerakli qismni o'zi almashtiradi.

SPA ikkita muammoni hal qilishi kerak:

  1. Manzil satri ilovadagi joyni ko'rsatsin — havolani ulashsa, do'st aynan o'sha joyni ko'rsin.
  2. «Orqaga» va «Oldinga» tugmalari foydalanuvchi kutganidek ishlasin.

O'tgan darsda vazifalar filtrni URL'dan o'qiydigan bo'ldi (?filtr=faol). Lekin tugma bosilganda URL o'zgarmaydi. Bugun buni ham, «Orqaga» tugmasini ham hal qilamiz.

O'xshatish: kitobdagi xatcho'p. Sahifa raqamini kitobni yopmasdan o'zgartirasiz (pushState), orqaga varaqlasangiz — oldingi xatcho'pga qaytasiz (popstate).

2. location: joriy manzil

2.1 Manzilni o'qish

location — joriy sahifaning manzilini ko'rsatadigan va boshqaradigan global obyekt. Uning o'qish qismi o'tgan darsdagi URL bilan bir xil: href, protocol, host, hostname, port, pathname, search, hash, origin. Faqat searchParams yo'q — parametrlarni o'qish uchun new URLSearchParams(location.search) yoki new URL(location.href).searchParams.

http://127.0.0.1:5500/menyu?turkum=taom#osh sahifasida:

js
console.log(location.pathname); // /menyu
console.log(location.search); // ?turkum=taom
console.log(location.hash); // #osh
const p = new URLSearchParams(location.search);
console.log(p.get("turkum")); // taom

2.2 Boshqa sahifaga o'tish: assign, replace, reload

location ga yozish — haqiqiy o'tish: brauzer yangi sahifani yuklaydi.

Usul Nima qiladi Tarixda
location.href = url yoki location.assign(url) Yangi sahifani yuklaydi Yangi yozuv — «Orqaga» bilan qaytsa bo'ladi
location.replace(url) Yangi sahifani yuklaydi Joriy yozuv almashadi — orqaga qaytib bo'lmaydi
location.reload() Shu sahifani qayta yuklaydi O'zgarmaydi
location.hash = "osh" Faqat # qismini o'zgartiradi — sahifa yuklanmaydi Yangi yozuv

replace qachon kerak? Mehmon bron formasini yubordi va "Rahmat" sahifasiga o'tkazildi. Agar u «Orqaga» bossa, formaga qaytib, uni qayta yuborishi mumkin. location.replace("/rahmat") bilan forma tarixdan "o'chadi". Kirish sahifasidan keyingi yo'naltirish ham ko'pincha replace — «Orqaga» foydalanuvchini qaytadan kirish formasiga olib bormasligi uchun.

Oxirgi qatorga e'tibor bering: hash ni o'zgartirish sahifani yuklamaydi. Brauzer faqat id si mos elementga aylantiradi. Shu xususiyat SPA'larning birinchi avlodiga asos bo'lgan ("Hash routing" bo'limida).

Diqqat: location.href = foydalanuvchiMatni — o'tgan darsdagi ochiq yo'naltirish va javascript: xavfi aynan shu yerda. Tashqi manbadan kelgan manzilga o'tishdan oldin uni ichkiManzil kabi funksiya bilan tekshiring (URL va URLSearchParams, "Hujumchi nigohi").

3. History API

3.1 Tarix — sahifalar to'plami

Brauzer har tab uchun sessiya tarixini yuritadi: siz ko'rgan sahifalar ro'yxati va "hozir qayerdasiz" ko'rsatkichi. «Orqaga» — ko'rsatkichni bir qadam chapga suradi, «Oldinga» — o'ngga. history obyekti shu ro'yxatga JavaScript'dan kirish:

  • history.back(), history.forward() — «Orqaga»/«Oldinga» tugmalari bilan bir xil.
  • history.go(-2) — ikki qadam orqaga; history.go(0) — qayta yuklash.
  • history.length — shu tabdagi yozuvlar soni (boshqa saytlarniki ham kiradi, ularning manzilini esa ko'rib bo'lmaydi — maxfiylik uchun).

3.2 pushState va replaceState

Asosiy ikki metod — sahifani yuklamasdan tarixni o'zgartiradi:

js
history.pushState({ bolim: "menyu" }, "", "/menyu");
console.log(location.pathname); // /menyu
console.log(history.state); // {bolim: 'menyu'}

history.replaceState({ filtr: "faol" }, "", "?filtr=faol");
console.log(location.href); // http://127.0.0.1:5500/menyu?filtr=faol

Uchta argument:

  1. Holat (state) — shu yozuvga biriktiriladigan ma'lumot. Keyin history.state dan yoki popstate hodisasidan olinadi. U structured clone bilan saqlanadi (IndexedDB darsidagi kabi): oddiy obyekt — ha, funksiya — yo'q. Kerak bo'lmasa — null.
  2. Sarlavha — tarixiy sabab bilan qolgan, brauzerlar e'tiborsiz qoldiradi. Har doim "".
  3. URL — yangi manzil: to'liq yoki nisbiy (/menyu, ?filtr=faol, #osh). Faqat shu origin ichida — boshqa saytga "o'tkazib" bo'lmaydi.

Farqi nomida: "push" — "ustiga qo'shish", "replace" — "almashtirish".

  • pushState — tarixga yangi yozuv qo'shadi. «Orqaga» oldingisiga qaytaradi.
  • replaceState — joriy yozuvni almashtiradi. Tarix uzunligi o'zgarmaydi.

Ikkalasi ham sahifani yuklamaydi, serverga so'rov yubormaydi va hech qanday hodisa chiqarmaydi. Ekranni yangilash — sizning vazifangiz.

Origin'dan tashqariga yozishga urinsangiz — xato. Natija oynasida ham pushState aynan shu sabab bilan ishlamaydi: uning origin'i null. Chrome 154 da natijaga o'xshash sandbox'li iframe'da olingan xabar:

text
SecurityError: Failed to execute 'pushState' on 'History': A history state object with URL 'http://127.0.0.1:5500/menyu' cannot be created in a document with origin 'null' and URL 'about:srcdoc'.

Tarjimasi: "Xavfsizlik xatosi: History da pushState bajarilmadi: null origin'li va about:srcdoc manzilli hujjatda http://127.0.0.1:5500/menyu manzilli tarix yozuvini yaratib bo'lmaydi." Shuning uchun bu darsning pushState misollarini Live Server'da sinaysiz.

3.3 popstate: «Orqaga» bosildi

Foydalanuvchi «Orqaga» yoki «Oldinga» ni bosdi (yoki kod history.back() ni chaqirdi). Agar ikki yozuv ham shu sahifa ichida pushState bilan yaratilgan bo'lsa, brauzer sahifani yuklamaydi. U faqat manzilni almashtiradi va window da popstate hodisasini chiqaradi:

js
addEventListener("popstate", (event) => {
  console.log("Qaytildi:", location.pathname, event.state);
});

event.state — o'sha yozuvga pushState da berilgan holat (history.state bilan bir xil).

Butun aylanishni bir ko'rib chiqamiz:

sequenceDiagram
  participant F as Foydalanuvchi
  participant K as Sizning kod
  participant B as Brauzer tarixi
  F->>K: «Menyu» havolasini bosdi
  K->>K: preventDefault()
  K->>B: pushState(null, "", "/menyu")
  K->>K: chiz("/menyu")
  F->>B: «Orqaga» tugmasi
  B->>K: popstate (manzil: "/")
  K->>K: chiz("/")

Nimaga qarang: pushState dan keyin chizishni kod o'zi qiladi, popstate dan keyin ham — o'zi. Brauzer faqat manzilni va tarixni boshqaradi. Bu ikki holatning ikkalasida ham bitta chiz funksiyasi location.pathname ga qarab ishlaydi.

Tekshirib ko'ring: Sardor pushState dan keyin ekranni yangilash uchun popstate tinglovchisiga tayandi. Nega havola bosilganda ekran o'zgarmaydi?

Javob

pushState hech qanday hodisa chiqarmaydi — popstate faqat «Orqaga»/«Oldinga» (yoki history.back()/go()) da keladi. pushState dan keyin chizish funksiyasini o'zi chaqirishi kerak.

4. Oddiy SPA router

4.1 Fayllar

Router ("yo'naltiruvchi") — manzilga qarab qaysi sahifani chizishni hal qiladigan kod. Bitta index.html va bitta router.js dan iborat «Bahor» sayti yasaymiz. Live Server bilan oching.

index.html:

html
<!DOCTYPE html>
<html lang="uz">
<head>
  <meta charset="utf-8">
  <title>«Bahor»</title>
  <script type="module" src="/router.js"></script>
</head>
<body>
  <nav>
    <a href="/">Bosh sahifa</a>
    <a href="/menyu">Menyu</a>
    <a href="/bron">Bron</a>
    <a href="https://example.com">Tashqi sayt</a>
  </nav>
  <main>
    <h1 id="sarlavha" tabindex="-1"></h1>
    <p id="matn"></p>
  </main>
</body>
</html>

Havolalar — oddiy <a href>. JavaScript ishlamasa ham ular haqiqiy havola bo'lib qoladi. Bu yondashuv progressive enhancement (bosqichma-bosqich yaxshilash) deyiladi: asosiy ish JavaScript'siz ham bajariladi, JavaScript esa uni qulayroq qiladi. Skript yo'li /router.js — / bilan: /menyu sahifasida ham u ildizdan olinadi.

4.2 Router kodi

router.js:

js
// router.js — sahifani qayta yuklamay almashtirish
const SAHIFALAR = {
  "/": { sarlavha: "Bosh sahifa", matn: "«Bahor»ga xush kelibsiz!" },
  "/menyu": { sarlavha: "Menyu", matn: "Osh, lag'mon, manti, choy" },
  "/bron": { sarlavha: "Bron", matn: "Stol bron: 10:00–22:00" },
};

const sarlavha = document.getElementById("sarlavha");
const matn = document.getElementById("matn");

function chiz(yol) {
  const sahifa = Object.hasOwn(SAHIFALAR, yol)
    ? SAHIFALAR[yol]
    : { sarlavha: "Topilmadi", matn: `«${yol}» sahifasi yo'q.` };
  sarlavha.textContent = sahifa.sarlavha;
  matn.textContent = sahifa.matn;
  document.title = `${sahifa.sarlavha} — «Bahor»`;
  console.log("chizildi:", yol);
}

document.addEventListener("click", (event) => {
  const havola = event.target.closest("a[href]");
  if (havola === null || havola.origin !== location.origin) {
    return;
  }
  if (event.button !== 0 || event.ctrlKey || event.metaKey
    || event.shiftKey || havola.target === "_blank") {
    return;
  }
  event.preventDefault();
  if (havola.pathname !== location.pathname) {
    history.pushState(null, "", havola.href);
    chiz(location.pathname);
    sarlavha.focus();
  }
});

addEventListener("popstate", () => {
  chiz(location.pathname);
});

chiz(location.pathname);

To'rt qism:

  1. SAHIFALAR — yo'l → kontent jadvali. Haqiqiy ilovada bu yerda komponentlar yoki shablon funksiyalari turadi. Object.hasOwn — /toString kabi yo'l obyektning meros xususiyatini topib olmasligi uchun (filtrniOl dagi oq ro'yxat g'oyasi).
  2. chiz(yol) — yagona chizish funksiyasi: kontent, document.title va konsol. Topilmagan yo'l uchun — "Topilmadi" sahifasi.
  3. Bosish tinglovchisi — bitta, document da (Event delegation). U faqat ichki havolalarni ushlaydi. Tashqi sayt (origin boshqa), o'ng tugma, Ctrl/Cmd/Shift bilan bosish ("yangi tabda och") va target="_blank" — brauzerning o'ziga qoldiriladi. Shu tekshiruvlarsiz foydalanuvchi havolani yangi tabda ocha olmay qoladi.
  4. popstate — «Orqaga»/«Oldinga» da xuddi o'sha chiz.

Oxirgi qator — sahifa birinchi ochilganda joriy yo'lni chizish. Kimdir to'g'ridan-to'g'ri /bron havolasi bilan kirsa, darhol «Bron» chiqadi.

4.3 Sinab ko'ramiz

Chrome 154 da avtomatik sinov bilan tekshirildi (Live Server'dagidek oddiy statik server):

  1. «Menyu» va «Bron» ni bosing. Konsolda chizildi: /menyu, chizildi: /bron. Manzil satri — http://127.0.0.1:5500/bron, tab nomi — Bron — «Bahor».
  2. «Bron» ni yana bosing — hech narsa bo'lmaydi: bir xil yo'lga ikkinchi yozuv qo'shilmaydi.
  3. «Orqaga» → chizildi: /menyu. «Oldinga» → chizildi: /bron.
  4. DevTools → Network: bosishlarda HTML so'rovi yo'q — sahifa bir marta yuklangan.
  5. «Tashqi sayt» — oddiy o'tish, router unga tegmaydi.

4.4 Fokus va ekran o'quvchi

Oddiy o'tishda brauzer fokusni sahifa boshiga qo'yadi va ekran o'quvchi yangi sahifa nomini o'qiydi. SPA'da sahifa yuklanmaydi — bu ishni kod qilishi kerak. Aks holda Malika (ekran o'quvchi bilan ishlaydi) havolani bosib, hech narsa o'zgarmaganini eshitadi.

Shuning uchun router bosishdan keyin sarlavha.focus() qiladi: <h1 tabindex="-1"> dasturiy fokus oladi (Dinamik UI'da qulaylik). Ekran o'quvchi yangi sarlavhani o'qiydi, klaviatura foydalanuvchisi esa keyingi Tab da sahifa ichiga o'tadi. Tab nomi uchun document.title ni ham yangilaymiz — tab nomi va tarix ro'yxati shu bilan to'g'ri ko'rinadi. popstate da fokus qo'yilmaydi: «Orqaga» bosgan foydalanuvchi o'z joyida qolishni kutadi.

4.5 Sahifani yangilasa — 404

Endi /menyu sahifasida turib, F5 ni bosing. Live Server'da 404 chiqadi. Nega?

F5 — haqiqiy yuklanish. Brauzer serverdan /menyu ni so'raydi. Serverda esa faqat index.html va router.js bor — menyu degan fayl yo'q. Router hech qachon ishga tushmaydi, chunki sahifaning o'zi kelmadi.

Yechim — server sozlamasi: "fayl topilmasa, index.html ni qaytar" (SPA fallback). Biz shunday serverni sinaganimizda F5 dan keyin /bron sahifasi joyida qoldi. Turli joylarda:

  • Vite, Next.js dasturlash serverlari — o'zi qiladi (16- va 23-qismlarda).
  • Netlify — _redirects faylida /* /index.html 200 qatori.
  • Nginx — try_files $uri /index.html; (server darslarida).
  • GitHub Pages — bunday sozlama yo'q. Shuning uchun u yerdagi SPA'lar hash routing yoki ?parametr dan foydalanadi. vazifalar filtrni aynan shu sababli ?filtr= da saqlaydi: index.html?filtr=faol — mavjud fayl, 404 bo'lmaydi.

Tekshirib ko'ring: Router'da havola.origin !== location.origin tekshiruvi bo'lmasa, «Tashqi sayt» bosilganda nima bo'ladi?

Javob

pushState boshqa origin'dagi manzilni qabul qilmaydi va SecurityError tashlaydi. Undan oldin esa preventDefault() chaqirilgan — o'tish ham bekor bo'ladi. Natijada havola umuman ishlamaydi.

5. Hash routing

5.1 # dan keyingi qism

pushState 2010-yillarda paydo bo'lgan. Undan oldingi SPA'lar faqat # dan foydalangan: index.html#/menyu. Agar hash o'zgarsa, sahifa yuklanmaydi, tarixga yozuv tushadi va hashchange hodisasi keladi. Belgi # dan keyingisi serverga umuman yuborilmaydi (URL anatomiyasi) — shuning uchun 404 muammosi yo'q.

Hash routing natija oynasida ham ishlaydi. Havolalarni bosing:

html
<nav>
  <a href="#/">Bosh sahifa</a>
  <a href="#/menyu">Menyu</a>
  <a href="#/bron">Bron</a>
</nav>
<h1 id="sarlavha"></h1>
<script>
  const SAHIFALAR = {
    "/": "«Bahor»ga xush kelibsiz!",
    "/menyu": "Osh — 35 000, manti — 30 000",
    "/bron": "Stol bron qilish: 10:00–22:00",
  };

  function joriyYol() {
    return location.hash.slice(1) || "/";
  }

  function chiz() {
    const yol = joriyYol();
    const matn = Object.hasOwn(SAHIFALAR, yol)
      ? SAHIFALAR[yol]
      : "Topilmadi";
    document.getElementById("sarlavha").textContent = matn;
    console.log("chizildi:", yol);
  }

  addEventListener("hashchange", chiz);
  chiz();
</script>

Konsolda:

text
chizildi: /

«Menyu» ni bossangiz — chizildi: /menyu, «Bron» — chizildi: /bron. Havolalarni ushlash, preventDefault, pushState — hech biri kerak bo'lmadi: brauzer # havolani o'zi bajaradi, biz faqat hashchange ni tinglaymiz. location.hash.slice(1) — boshidagi # ni olib tashlash; bo'sh bo'lsa — bosh sahifa.

5.2 Qaysi birini tanlash

Hash (#/menyu) History (/menyu)
Server sozlamasi Kerak emas SPA fallback kerak
Manzil ko'rinishi # bilan Toza
Sahifa ichidagi #bo'lim havolalari To'qnashadi Ishlaydi
Server sahifani tayyorlab berishi (SSR) Mumkin emas — # serverga bormaydi Mumkin

Bugungi ilovalarning ko'pi — History API. Hash — server sozlab bo'lmaydigan joylarda (GitHub Pages, oddiy fayl hosting) yoki ichki vositalarda. SSR va server sahifalari — Next.js qismida.

6. Navigation API

History API'ning noqulay joylarini ko'rdingiz: havolalarni qo'lda ushlash, pushState dan keyin o'zi chizish, popstate alohida, bir nechta tekshiruv. Navigation API — buni bitta hodisaga yig'adigan yangi interfeys. Global navigation obyekti har bir o'tishdan oldin navigate hodisasini chiqaradi: havola bosilsa ham, «Orqaga» bosilsa ham, forma yuborilsa ham:

js
navigation.addEventListener("navigate", (event) => {
  const url = new URL(event.destination.url);
  if (!event.canIntercept || event.hashChange
    || event.downloadRequest !== null) {
    return;
  }
  event.intercept({
    handler() {
      chiz(url.pathname);
    },
  });
});

event.intercept(...) — "bu o'tishni o'zim bajaraman": brauzer sahifani yuklamaydi, manzilni o'zi yangilaydi va handler ni chaqiradi. canIntercept boshqa origin'ga o'tishda false — tashqi havolalar o'z-o'zidan chetlanadi. Havolalarni qo'lda ushlash, pushState va alohida popstate kerak emas. Chrome 154 da sinadik: havola, «Orqaga» va navigation.navigate("/bron") — uchalasi ham shu bitta tinglovchidan o'tdi.

web-features ma'lumotiga ko'ra Navigation API — Baseline 2026 (yanvar): Chrome 102, Firefox 147, Safari 26.2. Ya'ni yangi brauzerlarning hammasida bor, lekin eskiroq telefonlarda yo'q. Hozircha ko'p loyihalar History API'da qoladi; Navigation API'ni "progressive enhancement" sifatida ishlatish mumkin: if ("navigation" in window) { ... }.

7. Ko'p uchraydigan xatolar

7.1 pushState dan keyin chizishni unutish

Manzil o'zgardi, ekran — yo'q. Tuzatish: pushState va chiz doim juft; popstate faqat tarix bo'ylab harakatda.

7.2 Hamma havolani ushlash

Ctrl + bosish yangi tab ochmaydi, tashqi havolalar SecurityError beradi. Tuzatish: origin, tugma, modifikator tugmalar va target tekshiruvi.

7.3 Nisbiy skript va CSS yo'llari

<script src="router.js"> — SPA fallback bor serverda /menyu/tafsilot sahifasi yangilansa, brauzer /menyu/router.js ni qidiradi va topmaydi. Tuzatish: SPA'da yo'llar / bilan boshlansin.

7.4 Har o'zgarishda pushState

Qidiruv maydonidagi har harfni pushState qilsangiz, «Orqaga» harfma-harf o'chiradi. Tuzatish: mayda o'zgarishlar — replaceState, mazmunli o'tishlar — pushState.

7.5 Fokus va sarlavhani unutish

Ekran o'quvchi va tab nomi eski sahifada qoladi. Tuzatish: document.title va <h1 tabindex="-1"> ga fokus.

8. Mashqlar

1-mashq (oson): «Rahmat» sahifasiga to'g'ri o'tish

Bron formasi yuborilgach, mehmonni /rahmat ga yuborish kerak — shunday qilib, «Orqaga» uni formaga qaytarmasin. Qaysi qator to'g'ri: location.href = "/rahmat", location.assign("/rahmat") yoki location.replace("/rahmat")? Nega? Kirish (login) sahifasidan keyin-chi?

Yechim

location.replace("/rahmat"). U joriy yozuvni (forma sahifasini) tarixda almashtiradi, shuning uchun «Orqaga» formadan oldingi sahifaga olib boradi. href va assign bir xil ishlaydi — yangi yozuv qo'shadi, forma tarixda qoladi.

Kirishdan keyin ham replace: aks holda «Orqaga» foydalanuvchini kirish formasiga qaytaradi, u esa allaqachon kirgan. Server tomonida yo'naltirishda bu "POST'dan keyin yo'naltirish" (Post/Redirect/Get) naqshi deyiladi — backend qismida.

2-mashq (o'rta): Router'ga parametr

Router'ga /menyu/1, /menyu/2 kabi yo'llarni qo'shing: /menyu/ dan keyingi son — taom id si. Yangi taomniTop(yol) funksiyasi yo'ldan id ni ajratib, MENYU massivi ichidan taomni topsin; topilmasa yoki son bo'lmasa — null. Ishora: yol.split("/") — ["", "menyu", "2"]; Number.isInteger(Number(...)).

Yechim
js
const MENYU = [
  { id: 1, nom: "Osh", narx: 35000 },
  { id: 2, nom: "Lag'mon", narx: 28000 },
  { id: 3, nom: "Manti", narx: 30000 },
];

function taomniTop(yol) {
  const [, bolim, qism] = yol.split("/");
  const id = Number(qism);
  if (bolim !== "menyu" || !Number.isInteger(id)) {
    return null;
  }
  return MENYU.find((t) => t.id === id) ?? null;
}

console.log(taomniTop("/menyu/2"));
// { id: 2, nom: "Lag'mon", narx: 28000 }
console.log(taomniTop("/menyu/9")); // null
console.log(taomniTop("/menyu/abc")); // null
console.log(taomniTop("/bron/1")); // null

router.js dagi chiz ga qo'shiladi: avval SAHIFALAR dan qidiriladi, topilmasa — taomniTop(yol); u ham null bo'lsa — "Topilmadi". const [, bolim, qism] — destructuring'da birinchi elementni (bo'sh satr) tashlab ketish. Number("") — 0, Number("abc") — NaN; isInteger ikkinchisini to'xtatadi, 0 esa find da topilmaydi.

React Router, Vue Router kabi kutubxonalarda bu /menyu/:id ko'rinishida yoziladi — ichida aynan shunday ajratish. Brauzerda buning uchun URLPattern degan yangi API ham bor; uni hozir bilish shart emas.

3-mashq (qiyin): Qidiruv — replaceState bilan

Menyu sahifasida qidiruv maydoni bor. Har yozilgan harfda (input hodisasi) ro'yxat filtrlansin va qidiruv so'zi URL'ga ?q=... bo'lib yozilsin — lekin «Orqaga» harfma-harf qaytmasin. Sahifa ?q=osh bilan ochilsa, maydon va ro'yxat shu so'z bilan boshlansin. Bo'sh qidiruvda ?q URL'dan butunlay olib tashlansin.

Yechim
html
<label>Qidiruv <input id="qidiruv" type="search"></label>
<ul id="royxat"></ul>
<script type="module">
  const TAOMLAR = ["Osh", "Lag'mon", "Manti", "Ko'k choy"];
  const maydon = document.getElementById("qidiruv");
  const royxat = document.getElementById("royxat");

  function chiz(soz) {
    const kichik = soz.toLowerCase();
    const mos = TAOMLAR.filter(
      (t) => t.toLowerCase().includes(kichik),
    );
    royxat.replaceChildren(...mos.map((t) => {
      const li = document.createElement("li");
      li.textContent = t;
      return li;
    }));
  }

  maydon.addEventListener("input", () => {
    const soz = maydon.value.trim();
    const url = new URL(location.href);
    if (soz === "") {
      url.searchParams.delete("q");
    } else {
      url.searchParams.set("q", soz);
    }
    history.replaceState(null, "", url);
    chiz(soz);
  });

  maydon.value = new URL(location.href).searchParams.get("q") ?? "";
  chiz(maydon.value);
</script>

replaceState — tarixga yangi yozuv qo'shmaydi, shuning uchun «Orqaga» qidiruv sahifasidan oldingi sahifaga olib boradi. URL'ni searchParams bilan yig'dik — q=Ko'k choy ham to'g'ri kodlanadi (q=Ko%27k+choy). Live Server'da ?q=choy bilan ochib tekshiring: maydonda choy, ro'yxatda faqat Ko'k choy.

Faylni kurs/mashqlar/11/30-router/qidiruv.html ga saqlang:

bash
git add 11/30-router/qidiruv.html
git commit -m "11/30: qidiruv so'zi URL'da, replaceState bilan"

4-mashq: Vazifalar qadami — filtr URL'ga yoziladi

O'tgan darsda marshrut.js da filtrniOl() yozdik va feature/url-filtr branch'ini ochiq qoldirdik. Endi uni tugatamiz:

  1. marshrut.js ga filtrniYoz(filtr): "hammasi" bo'lsa — ?filtr URL'dan o'chirilsin, aks holda ?filtr=<filtr> qo'yilsin. URL o'zgarsa — history.pushState. O'zgarmasa (shu filtr qayta bosildi) — tarixga hech narsa yozilmasin.
  2. asosiy.js dagi filtrniTanla — holatni o'zgartirgach filtrniYoz(filtr) ni chaqirsin.
  3. asosiy.js — popstate da filtr URL'dan qayta o'qilsin (filtrniOl()), tahrir yopilsin va render() bo'lsin.
  4. Bitta nozik joy: holat.js dagi vazifaQosh "Bajarilgan" filtrida yangi vazifa qo'shilsa, filtrni "hammasi" ga o'tkazadi. URL ham ergashsin.

Ishora: filtrni pushState ning holat argumentida saqlash shart emas — URL'ning o'zi holat. popstate da filtrniOl() uni qayta o'qiydi.

Yechim

assets/js/marshrut.js — to'liq:

js
// marshrut.js — filtr holati URL'da: ?filtr=faol
const FILTRLAR = ["hammasi", "faol", "bajarilgan"];

export function filtrniOl(manzil = location.href) {
  const filtr = new URL(manzil).searchParams.get("filtr");
  return FILTRLAR.includes(filtr) ? filtr : "hammasi";
}

export function filtrniYoz(filtr) {
  const url = new URL(location.href);
  if (filtr === "hammasi") {
    url.searchParams.delete("filtr");
  } else {
    url.searchParams.set("filtr", filtr);
  }
  if (url.href !== location.href) {
    history.pushState(null, "", url);
  }
}

url.href !== location.href — bir xil filtrni ikki marta bosish tarixga ikkita bir xil yozuv qo'shmasin. "Hammasi" — standart holat, uni URL'da yozish shart emas: toza manzil …/vazifalar/ ham "hammasi" degani.

assets/js/asosiy.js — o'zgargan joylar:

js
import { filtrniOl, filtrniYoz } from "./marshrut.js";
// ...
function filtrniTanla(filtr) {
  if (!Object.hasOwn(boshMatnlar, filtr)) {
    return;
  }
  holat.filtr = filtr;
  holat.tahrirId = null;
  filtrniYoz(filtr);
  render();
}
// ...
forma.addEventListener("submit", (event) => {
  // ... xatolar tekshiruvi o'zgarmadi
  vazifaQosh(matn);
  filtrniYoz(holat.filtr);
  forma.reset();
  elonQil(`«${matn}» qo'shildi.`);
});
// ...
// Orqaga/Oldinga: filtr URL'dan qayta o'qiladi
addEventListener("popstate", () => {
  holat.filtr = filtrniOl();
  holat.tahrirId = null;
  render();
});

// Ishga tushirish
holat.filtr = filtrniOl();
holat.vazifalar.obuna(saqlaVaChiz);
render();

vazifaQosh dan keyin filtrniYoz(holat.filtr): filtr o'zgarmagan bo'lsa, filtrniYoz tarixga hech narsa yozmaydi; "bajarilgan" → "hammasi" bo'lgan bo'lsa — URL'dan ?filtr olib tashlanadi.

popstate da saqla yo'q — vazifalar o'zgarmadi, faqat ko'rinish. Fokusni ham siljitmaymiz: «Orqaga» bosgan foydalanuvchi o'z joyida qoladi.

Chrome 154 da avtomatik sinov bilan tekshirildi:

  • «Faol» → manzil ?filtr=faol, «Bajarilgan» → ?filtr=bajarilgan; «Bajarilgan» qayta bosilsa — tarix uzaymaydi.
  • «Orqaga» → «Faol» tugmasi bosilgan holatda (aria-pressed="true") va ro'yxat faqat faollar; yana «Orqaga» → manzil toza, «Hammasi».
  • ?filtr=bajarilgan havolasi bilan ochish — darhol bajarilganlar; ?filtr=constructor — «Hammasi».
  • ?filtr=bajarilgan da yangi vazifa qo'shilsa — filtr «Hammasi» ga, manzil ham toza bo'ladi.

Endi do'stingizga https://login.github.io/vazifalar/?filtr=bajarilgan havolasini yuborsangiz, u o'zining bajarilgan vazifalarini ko'radi, siznikini emas: ro'yxat har brauzerning o'z localStorage ida. Ro'yxatni ham ulashish uchun server kerak — 11-qism yakuniy loyihasida.

bash
git add assets/js/marshrut.js assets/js/asosiy.js
git commit -m "Filtr: filtrni URL'ga yoz, Orqaga bilan qaytar"
git switch main
git merge feature/url-filtr
git branch -d feature/url-filtr
git push

9. Real ishda

  • Router kutubxonalari. React Router, TanStack Router, Vue Router — ichida bugungi pushState + popstate (yangilarida Navigation API) va /menyu/:id kabi parametr ajratish. Next.js'ning <Link> i ham shu tamoyil ustida. React — 17-qismda; hozir bilish shart emas.
  • Holat URL'da. Filtr, saralash, sahifa raqami, ochiq tab — URL'da bo'lsa, foydalanuvchi uni ulasha oladi va F5 dan keyin ham yo'qotmaydi. Bu yaxshi UX belgisi.
  • Deploy. "Sahifani yangilasam 404" — SPA'ni birinchi marta joylashtirgan dasturchining eng ko'p savoli. Javob: SPA fallback.
  • Intervyu. "pushState va location.href farqi?", "SPA routing qanday ishlaydi?", "Hash routing nima?" — frontend intervyusida tez-tez beriladi.

Xulosa

  • location — joriy manzil; assign/href — yangi yozuv bilan yuklash, replace — yozuvni almashtirib yuklash, reload — qayta yuklash, hash — yuklamasdan.
  • history.pushState(holat, "", url) va replaceState — yuklamasdan manzil va tarixni o'zgartiradi, hodisa chiqarmaydi; faqat shu origin.
  • popstate — «Orqaga»/«Oldinga» da keladi; ekranni location ga qarab o'zingiz chizasiz.
  • Router: ichki havolalarni ushlash (origin, tugma, modifikatorlar), pushState + chiz, popstate + chiz, fokus va document.title.
  • History routing serverda SPA fallback talab qiladi; hash routing talab qilmaydi. Navigation API — kelajakdagi yagona navigate hodisasi.

Keyingi dars: Oyna va tablar orasida aloqa — bir saytning ikki tabi bir-biriga qanday xabar beradi: BroadcastChannel, postMessage, window.open va vazifalar ning tablar aro sinxroni.

Manbalar

  • HTML Living Standard: "History", "Location", "Navigation API" — html.spec.whatwg.org
  • MDN: "History API", "Working with the History API", "Window: popstate event", "Navigation API" — developer.mozilla.org
  • Chrome for Developers: "Modern client-side routing: the Navigation API" — developer.chrome.com
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
location va History API: oddiy SPA router yozish — IlmHamroh