Mundarija (42)
- Bu darsda
- 1. Nega bu kerak?
- 2. Nega immutability: beshta foyda
- 2.1 O'zgarishni bir zumda sezish
- 2.2 Tarix va "bekor qilish" bepul
- 2.3 Kutilmagan o'zgarishdan himoya
- 2.4 Asinxron koddagi xavfsizlik
- 2.5 Xatoni topish oson
- 2.6 Narxi
- 3. Naqshlar: har tur uchun yangilash
- 4. Object.freeze chegaralari
- 4.1 Map, Set va Date muzlamaydi
- 4.2 Private maydonlar ham
- 4.3 Typed array'ni muzlatib bo'lmaydi
- 4.4 freeze — kafolat emas, signal
- 5. structuredClone chegaralari
- 5.1 Funksiyalar — xato
- 5.2 Klass nusxasi oddiy obyektga aylanadi
- 5.3 Yana nimalar yo'qoladi
- 5.4 Qachon nima
- 6. Structural sharing chuqur
- 6.1 Faqat yo'l nusxalanadi
- 6.2 O'lchov: qanchalik arzon?
- 6.3 Juda katta ma'lumot: maxsus tuzilmalar
- 7. Immer g'oyasi
- 7.1 Muammo: uch qavatli spread
- 7.2 Immer: "joyida yozing, o'zgarmas oling"
- 7.3 Ichida nima bor? Proxy!
- 8. Ko'p uchraydigan xatolar
- 8.1 freeze ni chuqur deb o'ylash
- 8.2 Muzlagan Map ga ishonish
- 8.3 Klass nusxasini structuredClone qilish
- 8.4 Holatni har safar structuredClone qilish
- 8.5 Retsept ichida qaytarish va o'zgartirishni aralashtirish
- 9. Mashqlar
- 1-mashq (oson): Kalitni o'chirish va Map yangilash
- 2-mashq (o'rta): Bekor qilish va qaytarish
- 3-mashq (qiyin): Mini-Immer
- 4-mashq: Vazifalar qadami — tarix va umumiy qismlar
- 10. Real ishda
- Xulosa
- Manbalar
JavaScript immutability chuqur: Object.freeze, structuredClone va structural sharing
Qisqacha: Immutability — ma'lumotni joyida o'zgartirmaslik: o'zgarish kerak bo'lsa, yangi nusxa yasaladi, eskisi tegilmay qoladi. Shunda "nima o'zgardi?" savoliga bitta
===javob beradi, "bekor qilish" esa o'z-o'zidan ishlaydi. Bu qimmat emas: faqat o'zgargan qism yangidan yasaladi, qolgani eski va yangi holatda umumiy (structural sharing).Object.freezevastructuredClonening chegaralari bor — darsda ularni aniq ko'ramiz. Immer kutubxonasi esa "joyida o'zgartirgandek" yozib, o'zgarmas natija olishga imkon beradi.
Bu darsda
- Immutability'ning beshta amaliy foydasini va narxini aytib bera olasiz.
- Obyekt, massiv,
MapvaSetni o'zgartirmasdan yangilash naqshlarini qo'llaysiz. Object.freezevastructuredCloneqayerda yetmasligini aniq misollar bilan bilasiz.- Structural sharing nega arzonligini o'lchov bilan tushuntira olasiz.
- Immer qanday ishlashini va uning ichida
Proxyturganini tushunasiz.
Oldin bilishingiz kerak: Ichma-ich ma'lumotni immutable yangilash, Obyektni himoyalash: freeze, seal, preventExtensions, FP g'oyalari va deklarativ uslub, Proxy va Reflect.
1. Nega bu kerak?
Oldingi darsda FP g'oyalarini ko'rdik va uchinchisini — "ma'lumot o'zgartirilmaydi" — keyinga qoldirdik. Bugun shu g'oyani oxirigacha ochamiz.
Ikki so'zni eslab olamiz. Mutatsiya (mutation) — mavjud obyekt yoki massivning ichini joyida o'zgartirish: push, obj.x = 1, delete. Immutability (o'zgarmaslik) — buning teskarisi: ma'lumot yaratilgandan keyin o'zgartirilmaydi, o'zgarish kerak bo'lsa — yangi nusxa yasaladi.
Siz buni amalda bilasiz. Immutable yangilash darsida ichma-ich obyektni yo'l bo'ylab spread bilan yangiladingiz. freeze darsida obyektni muzlatdingiz. Lekin ikki savol ochiq qoldi: nega bunga shuncha kuch sarflash kerak? Va bu vositalar qayerda pand beradi?
«Bahor»dagi yangi voqea. Stajyor dasturchi Sardor kassa dasturiga "bekor qilish" tugmasini qo'shdi. U har o'zgarishdan oldin holatni tarixga saqladi:
const tarix = [];
const holat = { buyurtma: { taomlar: ["Osh"] } };
tarix.push(holat);
holat.buyurtma.taomlar.push("Manti");
tarix.push(holat);
console.log(tarix[0].buyurtma.taomlar); // [ 'Osh', 'Manti' ]
console.log(tarix[0] === tarix[1]); // trueTarixda ikkita yozuv bor — lekin ikkalasi ham bitta obyekt. Sabab: tarix.push(holat) obyektning nusxasini emas, unga havolani saqladi (Havola semantikasi). Keyingi push("Manti") o'sha obyektni o'zgartirdi — demak, "tarixdagi" yozuv ham o'zgardi. "Bekor qilish" bosilsa, eski holat o'rniga yana o'sha yangi holat chiqadi. Tarix — ishlamaydigan bezak.
Hayotdan o'xshatish: bankdagi hisob ko'chirmasi (vypiska). Bank hech qachon kechagi qatorni o'chirib, ustiga yangi summani yozmaydi. Har amal — yangi qator. Shuning uchun istalgan kungi qoldiqni ko'rish mumkin va hech kim "kecha qancha edi?" deb bahslashmaydi. Immutable ma'lumot — xuddi shu ko'chirma: yozilgan narsa o'zgarmaydi, o'zgarish — yangi yozuv.
2. Nega immutability: beshta foyda
2.1 O'zgarishni bir zumda sezish
Ma'lumot joyida o'zgartirilmasa, "o'zgardimi?" degan savolga === javob beradi. Yangi obyekt — demak o'zgargan. O'sha obyekt — demak o'zgarmagan, ichiga qarash shart emas:
let chizishlarSoni = 0;
let oxirgiHolat = null;
function chiz(holat) {
if (holat === oxirgiHolat) return;
oxirgiHolat = holat;
chizishlarSoni++;
}
const h1 = { filtr: "hammasi", vazifalar: [] };
chiz(h1);
chiz(h1);
const h2 = { ...h1, filtr: "faol" };
chiz(h2);
console.log(chizishlarSoni); // 2Ikkinchi chiz(h1) hech narsa qilmadi — holat o'sha. h2 esa yangi obyekt, demak o'zgarish bor. Ichma-ich maydonlarni birma-bir solishtirish shart emas: bitta === yetarli.
Mutatsiya bo'lsa, bu tekshiruv aldanadi: h1.filtr = "faol"; chiz(h1) — obyekt o'sha, chiz hech narsa qilmaydi, ekran eskirib qoladi. React (interfeys qurish kutubxonasi, 17-qismda o'rganamiz) aynan shu sababdan holatni joyida o'zgartirishni "ko'rmaydi".
2.2 Tarix va "bekor qilish" bepul
Har o'zgarish yangi holat yasasa, eski holatlar o'z-o'zidan saqlanadi. Sardorning tarixini to'g'rilaymiz:
const tarix = [{ buyurtma: { taomlar: ["Osh"] } }];
const oxirgi = tarix.at(-1);
tarix.push({
...oxirgi,
buyurtma: {
...oxirgi.buyurtma,
taomlar: [...oxirgi.buyurtma.taomlar, "Manti"],
},
});
console.log(tarix[0].buyurtma.taomlar); // [ 'Osh' ]
console.log(tarix[1].buyurtma.taomlar); // [ 'Osh', 'Manti' ]Endi "bekor qilish" — shunchaki oldingi elementga qaytish. Mashhur vositalar ham shu g'oyada ishlaydi. Masalan, Redux (ilova holatini saqlash kutubxonasi) ning brauzer qo'shimchasida "vaqt bo'ylab sayohat" (time travel debugging) bor: har holat saqlangan, istalganiga qaytib, ilova o'sha paytda qanday ko'ringanini ko'rish mumkin. Redux'ni keyingi qismlarda o'rganamiz.
2.3 Kutilmagan o'zgarishdan himoya
Funksiyaga obyekt berdingiz — u uni o'zgartirib qo'ydimi? Mutatsiyali kodda buni bilish uchun funksiyaning ichini o'qish kerak. Immutable kelishuvda javob oldindan ma'lum: hech kim hech narsani joyida o'zgartirmaydi. Bu xatoni Havola semantikasi darsida ko'rgan edik: nusxa deb o'ylagan narsa aslida o'sha obyekt bo'lib chiqdi.
2.4 Asinxron koddagi xavfsizlik
Asinxron kodda await dan oldin va keyin holat boshqacha bo'lishi mumkin: kutish paytida boshqa kod uni o'zgartiradi. Immutable holatda esa siz ushlab turgan obyekt hech qachon o'zgarmaydi:
const holat = { vazifalar: ["Non olish"] };
async function saqla(nusxa) {
await new Promise((tayyor) => setTimeout(tayyor, 100));
console.log("Saqlandi:", nusxa.vazifalar);
}
const jarayon = saqla(holat);
const yangiHolat = {
...holat,
vazifalar: [...holat.vazifalar, "Choy"],
};
await jarayon;
console.log("Hozir:", yangiHolat.vazifalar);Konsolda:
Saqlandi: [ 'Non olish' ]
Hozir: [ 'Non olish', 'Choy' ]saqla 100 ms kutdi, shu orada holat yangilandi. Lekin saqla qo'lidagi obyektni hech kim joyida o'zgartirmadi — yangilanish yangi obyektga yozildi. Shuning uchun u aynan chaqirilgan paytdagi holatni saqladi. Agar holat.vazifalar.push("Choy") yozilganda, saqlangan ro'yxatga kutilmaganda "Choy" ham tushib qolardi — race condition ning eng sodda ko'rinishi.
2.5 Xatoni topish oson
Holat faqat yangi nusxa orqali o'zgarsa, har o'zgarishning aniq joyi bor: yangi obyekt yasalgan qator. Mutatsiyali kodda esa xususiyatni kodning istalgan joyi o'zgartirgan bo'lishi mumkin.
2.6 Narxi
Halol bo'laylik, immutability tekin emas:
- Ko'proq obyekt. Har o'zgarish — yangi obyektlar. Ular xotirani egallaydi, eskilarini axlat yig'uvchi tozalaydi (Xotira boshqaruvi).
- Ko'proq yozuv. Uch qavatli spread — mutatsiyaning bitta qatoriga nisbatan uzun va xatoga moyil.
- Intizom. Til buni majburlamaydi. Bitta
push— va kelishuv buzildi.
Birinchi narxni structural sharing kamaytiradi, ikkinchisini — Immer. Uchinchisiga — freeze va testlar. Bularni birma-bir ko'ramiz.
Tekshirib ko'ring: «O'zgarishni bir zumda sezish» bo'limidagi
chizfunksiyasi ichma-ich obyektni to'liq solishtirish o'rniga faqat===ishlatadi. Bu qachon xato natija beradi?
Javob
Kimdir holatni joyida o'zgartirsa: obyekt o'sha, === true, chiz "o'zgarish yo'q" deb hech narsa qilmaydi — ekran eskiradi. === tekshiruvi faqat hamma immutable kelishuvga amal qilganda ishonchli. Shuning uchun bu ikki narsa doim birga yuradi.
3. Naqshlar: har tur uchun yangilash
Immutable yangilash darsida obyekt va massiv naqshlarini ko'rgan edik. Bu yerda ularni Map, Set va xususiyatni o'chirish bilan to'ldiramiz:
| Amal | Mutatsiya () | Immutable () |
|---|---|---|
| Xususiyat qo'shish | obj.x = 1 |
{ ...obj, x: 1 } |
| Xususiyatni o'chirish | delete obj.x |
const { x, ...qolgan } = obj |
| Massivga qo'shish | arr.push(a) |
[...arr, a] |
| Massivda almashtirish | arr[i] = a |
arr.with(i, a) |
| Massivdan olib tashlash | arr.splice(i, 1) |
arr.toSpliced(i, 1) |
Map ga yozish |
map.set(k, v) |
new Map(map).set(k, v) |
Set ga qo'shish |
set.add(a) |
new Set(set).add(a) |
Ikki yangi naqshni ishlatib ko'ramiz:
const mehmon = {
ism: "Dilshod aka",
tel: "+998 90 000 00 00",
_ichki: 7,
};
const { _ichki, ...ochiqMalumot } = mehmon;
console.log(Object.keys(ochiqMalumot)); // [ 'ism', 'tel' ]
console.log("_ichki" in mehmon); // true
const narxlar = new Map([["osh", 35000], ["manti", 30000]]);
const yangiNarxlar = new Map(narxlar).set("osh", 38000);
const ikkalasi = [narxlar.get("osh"), yangiNarxlar.get("osh")];
console.log(ikkalasi); // [ 35000, 38000 ]Birinchi naqsh — destructuring va rest: kerakmas kalit alohida o'zgaruvchiga "olinadi", qolgani yangi obyektga yig'iladi. Asl mehmon tegilmadi.
Ikkinchisi: new Map(narxlar) — sayoz nusxa, keyin set nusxaga yoziladi. set Map ning o'zini qaytaradi, shuning uchun bir qatorda yozdik. Set uchun ham xuddi shunday, yoki to'plam metodlari — union, difference — ular ham doim yangi Set qaytaradi.
Jadvaldan to'ldiring: massivdagi bitta elementni asl massivga tegmasdan almashtiradigan metod — .
4. Object.freeze chegaralari
freeze darsida uning asosiy chegarasini ko'rdik: u sayoz — ichma-ich obyektlarni muzlatmaydi. Bundan tashqari to'rt chegarasi bor. Ular kamroq ma'lum va shuning uchun xavfliroq.
4.1 Map, Set va Date muzlamaydi
const narxlar = Object.freeze(new Map([["osh", 35000]]));
narxlar.set("osh", 99000);
const ochilish = Object.freeze(new Date("2026-10-05T02:00:00Z"));
ochilish.setUTCHours(5);
console.log(narxlar.get("osh")); // 99000
console.log(ochilish.toISOString()); // 2026-10-05T05:00:00.000Z
console.log(Object.isFrozen(narxlar)); // trueisFrozen true deydi — lekin narx ham, vaqt ham o'zgardi, xato ham chiqmadi. Sabab Proxy darsidagi bilan bir xil: Map va Date ma'lumotni xususiyatlarda emas, ichki slotda saqlaydi. Ichki slot — obyekt ichidagi, koddan ko'rinmaydigan yashirin joy. freeze esa faqat ko'rinadigan xususiyatlarni qotiradi. set va setUTCHours ichki slotga yozadi — freeze uni ko'rmaydi.
Holatda Map yoki Date bo'lsa, ularni muzlatish befoyda. Ularni immutable kelishuv bilan ishlating (new Map(eski).set(...)) yoki holatda oddiy obyekt va ISO satr saqlang.
4.2 Private maydonlar ham
Klassning # maydonlari ham xususiyat emas:
class Hisoblagich {
#soni = 0;
oshir() {
this.#soni++;
return this.#soni;
}
}
const muzlagan = Object.freeze(new Hisoblagich());
console.log(muzlagan.oshir()); // 1
console.log(muzlagan.oshir()); // 2Obyekt "muzlagan", lekin ichki holati o'zgaryapti. Private maydonlar obyektga emas, klassning maxfiy jadvaliga tegishli — freeze u yerga yetmaydi.
4.3 Typed array'ni muzlatib bo'lmaydi
Object.freeze(new Uint8Array([1, 2, 3]));TypeError: Cannot freeze array buffer views with elementsTarjimasi: "Elementli array buffer ko'rinishlarini muzlatib bo'lmaydi." Typed array elementlari ham xususiyat emas, xotiradagi baytlar — ularni "faqat o'qiladigan" qilish mumkin emas. Bu holatda freeze hech bo'lmaganda ochiq xato beradi.
4.4 freeze — kafolat emas, signal
Xulosa: freeze oddiy obyekt va massiv uchun yaxshi himoya, lekin "bu ma'lumot o'zgarmas" degan to'liq kafolat emas. Shuning uchun amalda ikki yondashuv bor:
- Ishlab chiqishda muzlatish. Kutubxonalar (masalan, Immer) holatni avtomatik muzlatadi. Kimdir tasodifan mutatsiya qilsa — qat'iy rejimda
TypeErrordarhol chiqadi. Bu xatoni ertaroq topish vositasi. - Kompilyatsiya paytidagi tekshiruv. TypeScript'da
readonlyvaas const— kod ishga tushishidan oldin "bu yerga yozib bo'lmaydi" deb ogohlantiradi. TypeScript'ni 15-qismda o'rganamiz, hozir bilish shart emas.
Tekshirib ko'ring:
const s = Object.freeze(new Set(["osh"]));dan keyins.add("manti")yozildi. Xato chiqadimi?s.sizenechaga teng?
Javob
Xato chiqmaydi, s.size — 2. Set ham Map kabi elementlarini ichki slotda saqlaydi. freeze faqat xususiyatlarni qotiradi, add esa slotga yozadi. Shuning uchun Set ni muzlatish o'rniga new Set(s).add("manti") naqshini ishlating.
5. structuredClone chegaralari
Sayoz va chuqur nusxalash darsida structuredClone ni chuqur nusxaning eng yaxshi vositasi deb tanishgan edik. U haqiqatan ham kuchli: Date, Map, Set, RegExp, typed array'larni to'g'ri nusxalaydi, hatto aylanma havolalarni ham (JSON chuqur darsida JSON bu yerda yiqilgan edi). Lekin u nimalarni yo'qotishini bilish kerak.
5.1 Funksiyalar — xato
const taom = {
nom: "Osh",
narxiniHisobla() { return 35000; },
};
structuredClone(taom);DataCloneError: narxiniHisobla() { return 35000; } could not be cloned.Tarjimasi: "narxiniHisobla() {...} nusxalanmaydi." Xabarda funksiyaning butun matni ko'rsatiladi. Chrome'da xabar oldidan yana bir bo'lak bor: Failed to execute 'structuredClone' on 'Window':. Funksiya — kod, ma'lumot emas. structuredClone esa faqat ma'lumotni nusxalaydi.
5.2 Klass nusxasi oddiy obyektga aylanadi
class Taom {
constructor(nom, narx) {
this.nom = nom;
this.narx = narx;
}
chegirma(foiz) {
return this.narx * (1 - foiz / 100);
}
}
const asl = new Taom("Osh", 35000);
const nusxa = structuredClone(asl);
console.log(nusxa); // { nom: 'Osh', narx: 35000 }
console.log(nusxa instanceof Taom); // false
console.log(typeof nusxa.chegirma); // undefinedXato chiqmadi — bu yomonroq. Nusxa endi Taom emas: prototip yo'qoldi, u bilan birga metodlar ham. nusxa.chegirma(10) keyinroq, boshqa joyda TypeError beradi va sababini topish qiyin bo'ladi.
5.3 Yana nimalar yo'qoladi
| Nima | structuredClone dan keyin |
|---|---|
| Funksiya, metod | DataCloneError |
| Klass prototipi | yo'qoladi — oddiy obyekt |
| Getter | oddiy qiymatga aylanadi (bir marta hisoblanadi) |
| Symbol kalitli xususiyat | tushib qoladi |
| Sanalmaydigan xususiyat | tushib qoladi |
Object.freeze |
yo'qoladi — nusxa muzlamagan |
| DOM elementi | DataCloneError |
Oxirgi qatordan oldingisi muhim: muzlatilgan holatni structuredClone qilsangiz, nusxa muzlamagan bo'ladi. Muzlatish — obyektning xususiyati, ma'lumotning emas.
5.4 Qachon nima
| Vaziyat | Vosita |
|---|---|
| Ilova holatidagi bitta maydonni yangilash | yo'l bo'ylab spread (structural sharing) |
| Butunlay mustaqil nusxa kerak (shablon, "nusxa olish" tugmasi) | structuredClone |
| Ma'lumotni serverga yuborish yoki saqlash | JSON.stringify |
| Metodli klass nusxasi | klassning o'z nusxa() metodi yoki konstruktor |
Tekshirib ko'ring:
vazifalarilovasidagiVazifaklassi nusxalarinistructuredClonebilan nusxalasangiz, nima bo'ladi? Klassda private#maydon bo'lsa-chi?
Javob
Nusxalar oddiy obyektlarga aylanadi: instanceof Vazifa — false, metodlar yo'q. Private maydonlar esa umuman nusxalanmaydi — ular xususiyat emas. Shuning uchun vazifalar da holat paket.js orqali oddiy ma'lumotga aylantirilib saqlanadi va qayta Vazifa.dan(...) bilan klassga o'raladi.
6. Structural sharing chuqur
6.1 Faqat yo'l nusxalanadi
Immutable yangilash darsida tuzilmani bo'lishish (structural sharing) atamasini kiritgan edik: yangi holat eskisining o'zgarmagan qismlarini qayta ishlatadi. Endi uni katta ro'yxatda ko'ramiz.
vazifalar da 10 000 ta vazifa bor. 5001-vazifa bajarildi deb belgilanadi. Immutable yangilash nimani yangidan yasaydi?
const vazifalar = Array.from({ length: 10000 }, (_, i) => ({
id: i + 1,
matn: `Vazifa ${i + 1}`,
bajarildi: false,
}));
const yangi = vazifalar.with(5000, {
...vazifalar[5000],
bajarildi: true,
});
const umumiylar = yangi.filter((v, i) => v === vazifalar[i]).length;
console.log(yangi === vazifalar); // false
console.log(umumiylar); // 9999
console.log(vazifalar[5000].bajarildi); // falseIkki yangi narsa yasaldi: massiv va bitta vazifa obyekti. Qolgan 9 999 ta vazifa — eski va yangi massivda ayni bir obyektlar (===). Sxemada:
flowchart TB
E["eski massiv"] --> V1["vazifa 1"]
E --> V2["... 9 998 ta ..."]
E --> VE["vazifa 5001<br/>bajarildi: false"]
Y["yangi massiv"] --> V1
Y --> V2
Y --> VY["vazifa 5001<br/>bajarildi: true"]Massivning o'zi baribir yangidan yasaldi — lekin u faqat 10 000 ta havola saqlaydi, vazifalarning o'zini emas. Havolani nusxalash — vazifani nusxalashdan ancha arzon.
6.2 O'lchov: qanchalik arzon?
Xuddi shu yangilashni uch usulda o'lchadik: with (bitta indeksni almashtirish), map (hamma elementni ko'rib, keraklisini almashtirish) va structuredClone (hammasini nusxalab, keyin o'zgartirish). O'lchovda har vazifaga to'rtinchi maydon — teglar massivi ham qo'shildi:
- with + spreadmassiv + 1 obyekt0,007 ms
- map + spread10 000 tekshiruv0,093 ms
- structuredClone20 000+ obyekt nusxasi10,9 ms
Manba: O'lchov: Node 24.21, Windows 11, Intel Core i5-12500H, performance.now, 100 yangilashning o'rtachasi, 9 o'lchovning medianasi, 2026-10-05
structuredClone with dan taxminan 1 500 barobar sekinroq. Sababi aniq: u 10 000 vazifa, 10 000 teglar massivi va massivning o'zini — 20 000 dan ortiq obyektni yangidan yasaydi. with esa bitta massiv va bitta obyekt. Sizning kompyuteringizda raqamlar boshqacha bo'ladi, lekin farqning tartibi shunday qoladi.
Boshqa tomondan qarang: 0,1 ms — bu map uchun. Foydalanuvchi bosgan tugmaga javob uchun bu sezilmaydi. Shuning uchun vazifalar kabi ilovalarda oddiy map + spread to'liq yetarli. structuredClone esa ilova holatini har bosishda yangilash uchun emas.
6.3 Juda katta ma'lumot: maxsus tuzilmalar
with ham massivni to'liq nusxalaydi — 10 000 havolani. Million elementli ro'yxatda bu ham sezila boshlaydi. Buning uchun doimiy tuzilmalar (persistent data structures) bor: ro'yxatni kichik bo'laklardan iborat daraxt qilib saqlaydi va yangilashda faqat bitta shoxni nusxalaydi. Ro'yxat o'n barobar o'ssa, ish deyarli o'zgarmaydi. Ular Immutable.js (5.1 versiya) kutubxonasida bor. Bunday "o'sish tezligi"ni o'lchash tili — Big-O — 14-qismda. Oddiy ilovalarda bu kerak bo'lmaydi.
Til darajasida ham urinish bo'lgan: Records & Tuples taklifi #{ ... } va #[ ... ] ko'rinishidagi chuqur o'zgarmas qiymatlarni qo'shmoqchi edi. 2025-yil aprelida TC39 (JavaScript standartini yozadigan qo'mita) yig'ilishida mualliflar bu taklifni qaytarib oldi. Uning GitHub sahifasi arxivlandi. G'oyaning bir qismi yangi, ancha kichik Composites taklifida davom etmoqda (hozir dastlabki, 1-bosqichda). Demak, bugun immutability — til kafolati emas, dasturchi kelishuvi va vositalar.
7. Immer g'oyasi
7.1 Muammo: uch qavatli spread
Ichma-ich holatni yangilash to'g'ri, lekin o'qishga og'ir:
const yangi = {
...holat,
buyurtma: {
...holat.buyurtma,
taomlar: holat.buyurtma.taomlar.map((t) =>
t.id === 2 ? { ...t, soni: t.soni + 1 } : t,
),
},
};Niyat oddiy — "2-taomning sonini bittaga oshir". Mutatsiya bilan bu bitta qator bo'lardi.
7.2 Immer: "joyida yozing, o'zgarmas oling"
Immer (hozir 11.1 versiya) — shu muammoni hal qiladigan kutubxona. Uning asosiy funksiyasi produce. Siz unga holat va "retsept" funksiyasini berasiz. Retsept parametr sifatida qoralama (draft) oladi — holatga o'xshagan, lekin "qoralama daftar" kabi vaqtinchalik nusxa. Siz qoralamani joyida o'zgartirgandek yozasiz. Natija — yangi, o'zgarmas holat, asl esa tegilmaydi:
import { produce } from "immer";
const holat = {
filtr: "hammasi",
vazifalar: [
{ id: 1, matn: "Non olish", bajarildi: false },
{ id: 2, matn: "Choy damlash", bajarildi: false },
],
};
const yangi = produce(holat, (qoralama) => {
qoralama.vazifalar[0].bajarildi = true;
});
const eskisi = holat.vazifalar[0].bajarildi;
console.log(eskisi, yangi.vazifalar[0].bajarildi);
console.log(yangi.vazifalar[1] === holat.vazifalar[1]);
console.log(Object.isFrozen(yangi));Scratch papkada npm i immer@11.1.21 bilan Node 24 da ishga tushirildi:
false true
true
trueUch qatorni o'qing:
- asl holat
falsebo'lib qoldi, yangisidatrue— mutatsiya asl obyektga tegmadi; - 2-vazifa eski va yangi holatda bitta obyekt — Immer structural sharing'ni o'zi qildi;
- natija muzlatilgan — keyin kimdir uni tasodifan o'zgartirsa, qat'iy rejimda
TypeErrorchiqadi.
Retsept hech narsani o'zgartirmasa, produce asl holatning o'zini qaytaradi — === tekshiruvi "o'zgarish yo'q" deydi.
Kutubxona ulash va npm paketlari bilan ishlashni 16-qismda chuqur o'rganamiz. Hozir g'oya muhim.
7.3 Ichida nima bor? Proxy!
qoralama — asl holat emas. Bu Proxy. Retsept qoralama.vazifalar[0].bajarildi = true deb yozganda, proxy'ning set trap'i ishlaydi. U asl obyektga yozmaydi — o'zgarishni eslab qoladi va o'zgargan yo'ldagi obyektlarni "nusxalangan" deb belgilaydi. Retsept tugagach, Immer faqat belgilangan yo'lni nusxalab, qolganini asldan oladi.
Ya'ni Immer — o'tgan uch darsning birikmasi: Proxy (ushlab qolish), FP (asl ma'lumotga tegmaslik) va structural sharing (arzon nusxa). Shunga o'xshash mini-versiyani o'zingiz 3-mashqda yozasiz.
Redux Toolkit (Redux'ning rasmiy to'plami) ichida Immer ishlatadi. Shuning uchun Redux Toolkit kodida state.soni++ kabi "mutatsiya" ko'rsangiz, qo'rqmang — u Immer'ning qoralamasi. React va Redux'ni keyingi qismlarda o'rganamiz.
Tekshirib ko'ring: Retsept hech narsani o'zgartirmasa —
produce(holat, (q) => {})— natija=== holatbo'ladimi? Bu nega foydali?
Javob
Ha, true. O'zgarish bo'lmasa, Immer yangi obyekt yasamaydi va aslning o'zini qaytaradi. Shunda «O'zgarishni bir zumda sezish» bo'limidagi === tekshiruvi "o'zgarish yo'q" deydi va ekran keraksiz qayta chizilmaydi.
8. Ko'p uchraydigan xatolar
8.1 freeze ni chuqur deb o'ylash
Object.freeze(holat) dan keyin holat.buyurtma.taomlar.push(...) bemalol ishlaydi. Tuzatish: chuqur muzlatish funksiyasi (freeze darsidagi chuqurMuzlat) yoki Immer.
8.2 Muzlagan Map ga ishonish
Object.isFrozen(map) — true, lekin map.set() ishlaydi. Tuzatish: Map, Set, Date ni immutable naqsh bilan yangilang: new Map(eski).set(k, v).
8.3 Klass nusxasini structuredClone qilish
Xato chiqmaydi, lekin metodlar yo'qoladi va keyinroq TypeError: nusxa.chegirma is not a function chiqadi. Tuzatish: klassning o'z nusxalash metodi yoki konstruktor orqali yangi nusxa.
8.4 Holatni har safar structuredClone qilish
"Xavfsiz bo'lsin" deb har o'zgarishda butun holatni nusxalash — sekin va === tekshiruvini buzadi: hamma narsa "o'zgargan" bo'lib ko'rinadi. Tuzatish: faqat o'zgargan yo'lni nusxalang.
8.5 Retsept ichida qaytarish va o'zgartirishni aralashtirish
Immer retseptida yoki qoralamani o'zgartiring, yoki butunlay yangi qiymat qaytaring — ikkalasini birga emas. Ikkalasi birga bo'lsa, Immer xato beradi:
Error: [Immer] An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.Tarjimasi: "Immer retsepti yangi qiymat qaytardi va qoralamani o'zgartirdi. Yo yangi qiymat qaytaring, yo qoralamani o'zgartiring." Ko'pincha bu ataylab emas, tasodifan bo'ladi: (q) => q.soni++ — figurali qavssiz arrow q.soni++ ning qiymatini qaytaradi. Tuzatish: arrow funksiyada figurali qavs qo'ying: (q) => { q.soni++; }, aks holda q.soni++ ning qiymati qaytib ketadi.
9. Mashqlar
1-mashq (oson): Kalitni o'chirish va Map yangilash
Ikki funksiya yozing. ichkiniOlibTashla(mehmon) — _ bilan boshlanadigan bitta _id kalitisiz yangi obyekt qaytarsin. narxniYangila(narxlar, taom, narx) — Map ning yangi nusxasini qaytarsin. Asl qiymatlar o'zgarmasin. Ishora: destructuring rest va new Map(...).
const mehmon = { _id: 7, ism: "Malika" };
console.log(ichkiniOlibTashla(mehmon)); // { ism: 'Malika' }Yechim
function ichkiniOlibTashla(mehmon) {
const { _id, ...qolgan } = mehmon;
return qolgan;
}
function narxniYangila(narxlar, taom, narx) {
return new Map(narxlar).set(taom, narx);
}
const mehmon = { _id: 7, ism: "Malika" };
console.log(ichkiniOlibTashla(mehmon)); // { ism: 'Malika' }
console.log(mehmon._id); // 7
const narxlar = new Map([["osh", 35000]]);
const yangi = narxniYangila(narxlar, "osh", 38000);
console.log(narxlar.get("osh"), yangi.get("osh")); // 35000 38000_id o'zgaruvchisi ishlatilmaydi — u faqat kalitni "chetga olish" uchun. Linter bunday o'zgaruvchi haqida ogohlantirishi mumkin. _ bilan boshlangan nom — "ataylab ishlatilmaydi" degan kelishuv.
2-mashq (o'rta): Bekor qilish va qaytarish
tarixYarat(boshlangich) funksiyasini yozing. U obyekt qaytarsin: hozir() — joriy holat, ozgartir(yangiHolat) — yangi holatni qo'shadi, bekorQil() — bir qadam orqaga, qaytar() — bir qadam oldinga. Holatlar massivda va joriy indeksda saqlansin (closure bilan). Yangi o'zgarish "qaytar" yo'lini kesib tashlasin. Ishora: slice(0, indeks + 1) va spread.
const t = tarixYarat({ taomlar: [] });
t.ozgartir({ taomlar: ["Osh"] });
t.ozgartir({ taomlar: ["Osh", "Manti"] });
t.bekorQil();
console.log(t.hozir()); // { taomlar: [ 'Osh' ] }Yechim
function tarixYarat(boshlangich) {
let holatlar = [boshlangich];
let indeks = 0;
return {
hozir: () => holatlar[indeks],
ozgartir(yangiHolat) {
holatlar = [...holatlar.slice(0, indeks + 1), yangiHolat];
indeks = holatlar.length - 1;
},
bekorQil() {
if (indeks > 0) indeks--;
},
qaytar() {
if (indeks < holatlar.length - 1) indeks++;
},
};
}
const t = tarixYarat({ taomlar: [] });
t.ozgartir({ taomlar: ["Osh"] });
t.ozgartir({ taomlar: ["Osh", "Manti"] });
t.bekorQil();
console.log(t.hozir()); // { taomlar: [ 'Osh' ] }
t.qaytar();
console.log(t.hozir()); // { taomlar: [ 'Osh', 'Manti' ] }
t.bekorQil();
t.ozgartir({ taomlar: ["Osh", "Choy"] });
t.qaytar();
console.log(t.hozir()); // { taomlar: [ 'Osh', 'Choy' ] }Oxirgi qadamga qarang: "Manti" li holatdan bekor qilib, yangi o'zgarish qildik — "qaytar" yo'li kesildi va qaytar() hech narsa qilmadi. Matn muharrirlaridagi Ctrl+Z / Ctrl+Y xuddi shunday ishlaydi. Bu tarix faqat holatlar immutable bo'lsa ishlaydi: kimdir t.hozir().taomlar.push(...) qilsa, tarixdagi holat buziladi.
3-mashq (qiyin): Mini-Immer
yasa(asl, retsept) funksiyasini (Immer'ning produce i kabi) yozing (faqat bir qavatli obyektlar uchun). U asl ni Proxy ga o'rasin: set trap'i asl obyektga emas, alohida ozgarishlar obyektiga yozsin; get trap'i avval ozgarishlar ga, keyin aslga qarasin. Retsept tugagach: haqiqiy o'zgarish bo'lmasa — asl ning o'zini qaytaring, bo'lsa — muzlatilgan yangi obyektni. Ishora: Object.hasOwn, Object.is va Reflect.get (Proxy va Reflect).
const sozlama = { ochilish: "07:00", yopilish: "23:00" };
const yangi = yasa(sozlama, (q) => { q.yopilish = "00:00"; });
console.log(yangi); // { ochilish: '07:00', yopilish: '00:00' }
console.log(sozlama.yopilish); // 23:00Yechim
function yasa(asl, retsept) {
const ozgarishlar = {};
const qoralama = new Proxy(asl, {
get(nishon, kalit, receiver) {
if (Object.hasOwn(ozgarishlar, kalit)) {
return ozgarishlar[kalit];
}
return Reflect.get(nishon, kalit, receiver);
},
set(nishon, kalit, qiymat) {
ozgarishlar[kalit] = qiymat;
return true;
},
});
retsept(qoralama);
const haqiqiy = Object.keys(ozgarishlar).filter(
(kalit) => !Object.is(ozgarishlar[kalit], asl[kalit]),
);
if (haqiqiy.length === 0) return asl;
return Object.freeze({ ...asl, ...ozgarishlar });
}
const sozlama = { ochilish: "07:00", yopilish: "23:00" };
const yangi = yasa(sozlama, (q) => {
q.yopilish = "00:00";
});
console.log(yangi, sozlama.yopilish);
const xuddiOzi = yasa(sozlama, (q) => {
q.ochilish = "07:00";
});
const ozimi = xuddiOzi === sozlama;
console.log(ozimi, Object.isFrozen(yangi)); // true trueKonsolda:
{ ochilish: '07:00', yopilish: '00:00' } 23:00
true trueIkkinchi chaqiruvda retsept qiymatni o'sha qiymatga "o'zgartirdi" — Object.is buni sezdi va asl obyekt qaytdi. Shunday qilib === tekshiruvi "o'zgarish yo'q" deydi va keraksiz chizish bo'lmaydi. Haqiqiy Immer xuddi shu g'oyani ichma-ich obyektlar, massivlar, Map va Set uchun qiladi: har ichki obyekt o'qilganda uni ham proxy'ga o'raydi.
4-mashq: Vazifalar qadami — tarix va umumiy qismlar
Mashqlar repoda kurs/mashqlar/12/23-immutability/tarix.mjs yarating. 2-mashqdagi tarixYarat ni vazifalar ro'yxati bilan sinang: boshlang'ich ro'yxatga yangi vazifa qo'shing, keyin bitta vazifani bajarilgan deb belgilang — ikkalasini immutable ([...royxat, yangi] va map + spread). Har qadamdan keyin oldingi holat bilan nechta vazifa umumiy (===) ekanini chiqaring. Oxirida bekorQil qilib, eski holat buzilmaganini tekshiring.
Yechim
function tarixYarat(boshlangich) {
let holatlar = [boshlangich];
let indeks = 0;
return {
hozir: () => holatlar[indeks],
ozgartir(yangiHolat) {
holatlar = [...holatlar.slice(0, indeks + 1), yangiHolat];
indeks = holatlar.length - 1;
},
bekorQil() {
if (indeks > 0) indeks--;
},
};
}
const umumiySoni = (a, b) => b.filter((v) => a.includes(v)).length;
const tarix = tarixYarat([
{ id: 1, matn: "Non olish", bajarildi: false },
{ id: 2, matn: "DOM darsini takrorlash", bajarildi: true },
]);
const r0 = tarix.hozir();
const yangiVazifa = { id: 3, matn: "Choy damlash", bajarildi: false };
tarix.ozgartir([...r0, yangiVazifa]);
const r1 = tarix.hozir();
console.log("qo'shish:", umumiySoni(r0, r1), "umumiy");
tarix.ozgartir(
r1.map((v) => (v.id === 1 ? { ...v, bajarildi: true } : v)),
);
const r2 = tarix.hozir();
console.log("belgilash:", umumiySoni(r1, r2), "umumiy");
tarix.bekorQil();
console.log(tarix.hozir() === r1, r1[0].bajarildi); // true falseKonsolda:
qo'shish: 2 umumiy
belgilash: 2 umumiy
true falseQo'shishda ikkala eski vazifa yangi ro'yxatda o'sha obyektlar bo'lib qoldi. Belgilashda uchtadan ikkitasi umumiy — faqat 1-vazifa yangidan yasaldi. bekorQil dan keyin r1 ning birinchi vazifasi hamon false — tarix buzilmagan. Terminalda: node 12/23-immutability/tarix.mjs.
git add 12/23-immutability
git commit -m "12/23: immutable tarix va umumiy qismlar"10. Real ishda
- React.
setStatevauseReducerholatni===bilan solishtiradi. Joyida o'zgartirilgan holat — ekranda yangilanmagan UI: React'dagi eng ko'p uchraydigan boshlovchi xatosi. - Redux Toolkit va Zustand. Ikkalasi ham holatni immutable deb hisoblaydi; Redux Toolkit ichida Immer ishlaydi, Zustand'da u ixtiyoriy qo'shimcha.
- Bekor qilish/qaytarish. Figma, Google Docs, matn muharrirlari va o'yinlarda tarix — holatlar ketma-ketligi. Immutability'siz uni ishonchli qurish qiyin.
- Kesh va memoization. "Kirish o'zgarmagan bo'lsa — eski natijani ber" —
===bilan arzon tekshiriladi. Memoization darsida shunga tayanamiz. - Intervyu. "
Object.freezechuqur ishlaydimi?", "structuredClonenimalarni nusxalamaydi?", "Structural sharing nima?", "Immer qanday ishlaydi?" — middle darajadagi savollar.
Xulosa
- Immutability foydalari:
===bilan tez o'zgarish aniqlash, bepul tarix va "bekor qilish", kutilmagan o'zgarishdan va asinxron poygadan himoya, oson debug; narxi — ko'proq obyekt va yozuv. - Naqshlar:
{ ...obj, x },const { x, ...qolgan } = obj,with/toSpliced,new Map(eski).set(),new Set(eski).add(). Object.freeze— sayoz;Map,Set,Dateva#maydonlarni to'xtatmaydi; typed array'ni umuman muzlatmaydi.structuredClone— funksiyada xato, klassni oddiy obyektga aylantiradi, symbol kalit vafreezeni yo'qotadi; ilova holatini yangilash uchun emas.- Structural sharing faqat o'zgargan yo'lni nusxalaydi: 10 000 vazifada
with— 0,007 ms,structuredClone— 11 ms. - Immer
Proxyqoralamasi bilan "joyida yozing — o'zgarmas oling" imkonini beradi va structural sharing'ni o'zi qiladi.
Keyingi dars: Kompozitsiya: compose va pipe — toza, o'zgarmas ma'lumot bilan ishlaydigan kichik funksiyalarni bitta katta funksiyaga ulash.
Manbalar
- MDN: "Object.freeze()", "structuredClone()", "The structured clone algorithm" — developer.mozilla.org
- Immer: "Introduction to Immer", "Pitfalls" — immerjs.github.io/immer
- Redux: "Immutable Update Patterns" — redux.js.org/usage/structuring-reducers/immutable-update-patterns
- TC39: "Record & Tuple" taklifi (qaytarib olingan, 2025-04-14 yig'ilishi; issue #394) — github.com/tc39/proposal-record-tuple; "Composites" (1-bosqich) — github.com/tc39/proposal-composites; tc39/proposals, "Inactive proposals" ro'yxati (2026-10-05 da tekshirildi)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!