Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ichma-ich obyektni yangilash
- 2.1 Sayoz spread yetmaydi
- 2.2 Yo'l bo'ylab nusxa
- 2.3 structuredClone emasmi?
- 2.4 Yangilash — toza funksiya
- 3. Massivlarni immutable yangilash
- 3.1 Asosiy amallar jadvali
- 3.2 Massiv ichidagi obyekt
- 3.3 Uch qavat: massiv ichidagi obyekt ichidagi massiv
- 3.4 Lug'at ko'rinishidagi obyekt
- 4. ?. — chuqur qiymatni xavfsiz o'qish
- 4.1 O'qishda
- 4.2 Tayinlashda — yo'q
- 5. Muzlatish bilan tekshirish
- 6. Ko'p uchraydigan xatolar
- 6.1 Faqat yuqori qavatni nusxalash
- 6.2 map ichida mutatsiya
- 6.3 sort, reverse, splice holatda
- 6.4 ?. tayinlashda
- 6.5 with ga -1 indeks
- 6.6 Meros xususiyatlar nusxaga tushmaydi
- 6.7 Spread tartibi adashgan
- 7. Mashqlar
- 1-mashq (oson): Sozlamadagi bitta qiymat
- 2-mashq (o'rta): Savatdan o'chirish va qo'shish
- 3-mashq (qiyin): Yo'l bo'yicha yangilash funksiyasi
- 4-mashq: Vazifalar qadami — immutable yangilash va modulni yakunlash
- 8. Real ishda
- Xulosa
- Manbalar
JavaScript'da immutable yangilash: ichma-ich obyekt va massivni asl nusxaga tegmasdan o'zgartirish
Qisqacha: Immutable yangilash — ma'lumotni joyida o'zgartirmay, o'zgargan yangi nusxa yasash. Ichma-ich obyektda o'zgargan qiymatgacha bo'lgan yo'ldagi har bir obyekt spread bilan nusxalanadi:
{ ...h, mijoz: { ...h.mijoz, ism: "Ali" } }, qolgan hammasi esa eski nusxa bilan umumiy qoladi. Massiv ichidagi obyekt —mapyokiwithbilan, qo'shish —[...arr, x], o'chirish —filter.
Bu darsda
- Nega ma'lumotni joyida o'zgartirish React kabi vositalarda (interfeys yasash kutubxonasi, 17-qismda o'rganamiz) xato beradi — tushuntira olasiz.
- Ichma-ich obyektni yo'l bo'ylab nusxa usuli bilan yangilay olasiz.
- Massiv ichidagi obyektni
mapvawithbilan, qo'shish va o'chirishni nusxa bilan qila olasiz. ?.bilan chuqur qiymatni xavfsiz o'qiy olasiz va nega u tayinlashda ishlamasligini bilasiz.vazifalarni muzlagan ma'lumot bilan ishlaydigan qilib qayta yozasiz va obyektlar modulini yakunlaysiz.
Oldin bilishingiz kerak: Object.* metodlari, Nusxa bilan o'zgartirish: toSorted, with, Sayoz va chuqur nusxalash, map, ?? va ?., Obyektni himoyalash.
1. Nega bu kerak?
Sayoz va chuqur nusxalash darsida bir naqshni ko'rgan edik: o'zgaradigan obyektni nusxalab, yangi qiymat bilan almashtirish. O'shanda "to'liq amaliyot — keyinroq" degan edik. Bugun shu va'dani bajaramiz.
Avval — nega aynan nusxa? «Bahor»dagi bron obyekti:
const bron = {
mijoz: { ism: "Malika", manzil: { shahar: "Toshkent", uy: 7 } },
taomlar: [{ id: 1, nom: "Osh", soni: 1 }],
};
const kechagi = bron;
bron.mijoz.manzil.uy = 12;
console.log(kechagi === bron); // true
console.log(kechagi.mijoz.manzil.uy); // 12Biz "kechagi holat"ni saqlab qo'ydik, keyin uy raqamini o'zgartirdik. Natija: kechagi holat ham o'zgardi! kechagi va bron — bitta obyekt (Havola semantikasi). Endi ikki muammo bor:
- O'zgarishni sezib bo'lmaydi. "Holat o'zgardimi?" degan savolga
kechagi === bron"yo'q" deydi. React (17-qismda o'rganadigan kutubxona; hozir bilish shart emas) aynan shunday solishtiradi —Object.isbilan (Object.*metodlari). Joyida o'zgartirilgan holatda havola o'sha, demak React ekranni yangilamaydi. - Tarix yo'qoladi. "Bekor qilish" (undo) tugmasi uchun eski holat kerak. Lekin eski holat yo'q — u yangisi bilan bitta obyekt.
Hayotdan o'xshatish: kassadagi chek. Otabek xato chek urdi. U eski chekni qog'ozdan o'chirib, ustiga yozmaydi. Yangi, to'g'ri chek uriladi, eskisi esa "bekor qilindi" deb tarixda qoladi. Buxgalter keyin ikkalasini ham ko'radi. Immutable yangilash — xuddi shunday: eski holat joyida qoladi, yangisi alohida yasaladi.
Immutable yangilash — bu atama bilan Nusxa bilan o'zgartirish darsida tanishgan edik: ma'lumotni o'zgartirish o'rniga, o'zgarishi kiritilgan yangi nusxasini yasash. U yerda — tekis massiv, bugun — ichma-ich obyektlar.
2. Ichma-ich obyektni yangilash
2.1 Sayoz spread yetmaydi
Birinchi urinish — bitta spread:
const bron = {
mijoz: { ism: "Malika", manzil: { shahar: "Toshkent", uy: 7 } },
};
const yangi = { ...bron };
yangi.mijoz.manzil.uy = 12;
console.log(bron.mijoz.manzil.uy); // 12Yana buzildi. { ...bron } faqat birinchi qavatni nusxaladi. yangi.mijoz — eski bron.mijoz ning o'zi. Ichiga yozish — eskisiga yozish.
2.2 Yo'l bo'ylab nusxa
To'g'ri usul — o'zgaradigan qiymatgacha bo'lgan yo'ldagi har bir obyektni nusxalash. uy gacha yo'l: bron → mijoz → manzil → uy. Demak uchta spread kerak:
const bron = {
mijoz: { ism: "Malika", manzil: { shahar: "Toshkent", uy: 7 } },
taomlar: [{ id: 1, nom: "Osh", soni: 1 }],
};
const yangi = {
...bron,
mijoz: {
...bron.mijoz,
manzil: { ...bron.mijoz.manzil, uy: 12 },
},
};
console.log(bron.mijoz.manzil.uy, yangi.mijoz.manzil.uy); // 7 12
console.log(yangi === bron); // false
console.log(yangi.mijoz === bron.mijoz); // false
console.log(yangi.taomlar === bron.taomlar); // trueIchkaridan o'qing:
{ ...bron.mijoz.manzil, uy: 12 }— manzilning nusxasi,uyalmashtirilgan.{ ...bron.mijoz, manzil: ... }— mijozning nusxasi,manzilyangisiga almashtirilgan.{ ...bron, mijoz: ... }— bronning nusxasi,mijozyangisiga almashtirilgan.
Oxirgi qatorga qarang: yangi.taomlar === bron.taomlar — true. Taomlar o'zgarmadi, shuning uchun nusxalanmadi — ikkala bron bitta massivni bo'lishadi. Bu xato emas, aynan maqsad: o'zgarmagan qism ortiqcha nusxalanmaydi.
Sxemada kuzating — yangi bron qaysi qismlarni yangidan yasadi va qaysilarini eskisidan oldi:
flowchart TB
E["eski bron"] --> EM["eski mijoz"] --> EA["eski manzil: uy 7"]
Y["yangi bron"] --> YM["yangi mijoz"] --> YA["yangi manzil: uy 12"]
E --> T["taomlar (umumiy)"]
Y --> TYo'ldagi uchta obyekt — yangi, yo'ldan tashqaridagi taomlar — umumiy. Bu usulning nomi — tuzilmani bo'lishish (structural sharing): yangi holat eski holatning o'zgarmagan qismlarini qayta ishlatadi. Shuning uchun immutable yangilash katta ma'lumotda ham arzon: ming taomli buyurtmada uy raqamini o'zgartirish uchun ming taom nusxalanmaydi.
Tekshirib ko'ring: Yuqoridagi
bronda faqat mijoz ismini "Malika Karimova" ga o'zgartirish uchun nechta spread kerak?yangi.mijoz.manzil === bron.mijoz.manzilbo'ladimi?
Javob
Ikkita: { ...bron, mijoz: { ...bron.mijoz, ism: "Malika Karimova" } }. Yo'l — bron → mijoz → ism, ism esa satr (primitiv, nusxa kerak emas). manzil yo'lda emas, shuning uchun u umumiy qoladi: true.
2.3 structuredClone emasmi?
"Butunini chuqur nusxalab, keyin bemalol o'zgartiraman" — ham yo'l:
const bron = {
mijoz: { ism: "Malika", manzil: { uy: 7 } },
taomlar: [{ id: 1, nom: "Osh", soni: 1 }],
};
const yangi = structuredClone(bron);
yangi.mijoz.manzil.uy = 12;
console.log(bron.mijoz.manzil.uy); // 7
console.log(yangi.taomlar === bron.taomlar); // falseIshladi — asl o'zgarmadi. Lekin taomlar ham nusxalandi, garchi u o'zgarmagan bo'lsa ham. Bu ikki narsaga olib keladi:
| Yo'l bo'ylab spread | structuredClone + o'zgartirish |
|
|---|---|---|
| Nima nusxalanadi | faqat yo'ldagi obyektlar | hamma narsa |
| O'zgarmagan qism | umumiy (=== — true) |
yangi (=== — false) |
| Katta ma'lumotda | arzon | qimmat |
| "Nima o'zgardi?" savoli | === bilan aniq javob |
hamma narsa "o'zgargan" ko'rinadi |
React kabi vositalar === bilan "bu qism o'zgardimi?" deb so'raydi. structuredClone dan keyin hamma qism "o'zgargan" bo'ladi va hamma narsa qayta chiziladi. Shuning uchun ilova holatida asosiy usul — yo'l bo'ylab spread. structuredClone esa — ma'lumotni butunlay mustaqil qilish kerak bo'lganda (masalan, "nusxa olish" tugmasi).
2.4 Yangilash — toza funksiya
Immutable yangilashni funksiyaga o'rasak, u alohida xususiyatga ega bo'ladi: eski holat kiradi, yangi holat chiqadi, tashqarida hech narsa o'zgarmaydi. Bu — Pure funksiya darsidagi toza funksiya:
function uyniAlmashtir(bron, uy) {
return {
...bron,
mijoz: {
...bron.mijoz,
manzil: { ...bron.mijoz.manzil, uy },
},
};
}
const b0 = { mijoz: { ism: "Malika", manzil: { uy: 7 } } };
const b1 = uyniAlmashtir(b0, 12);
const b2 = uyniAlmashtir(b1, 15);
const uylar = [b0, b1, b2].map((b) => b.mijoz.manzil.uy);
console.log(...uylar);Konsolda:
7 12 15Uch holat — uchalasi ham tirik. b0 — boshlang'ich, b1 — birinchi o'zgarishdan keyin, b2 — ikkinchisidan keyin. Hech biri boshqasini buzmadi. Bunday funksiyani sinash oson: bir xil kirishga doim bir xil natija, va natijani eskisi bilan solishtirish mumkin.
Real loyihalarda holatni boshqaradigan butun qism shunday funksiyalardan quriladi: "eski holat + amal → yangi holat". Bu naqsh reducer deb ataladi — nomi reduce dagi akkumulyatordan: har amal eski holatdan yangisini "yig'adi". Redux va React'ning useReducer i aynan shu g'oyaga qurilgan — ularni keyingi qismlarda ko'rasiz.
3. Massivlarni immutable yangilash
3.1 Asosiy amallar jadvali
Massivlar uchun asboblar allaqachon bor — map, filter va Nusxa bilan o'zgartirish darslaridan. Ularni bitta jadvalga yig'amiz:
| Amal | Joyida ( holatda) | Nusxa bilan () |
|---|---|---|
| Oxiriga qo'shish | arr.push(x) |
[...arr, x] |
| Boshiga qo'shish | arr.unshift(x) |
[x, ...arr] |
| O'chirish | arr.splice(i, 1) |
arr.filter(...), arr.toSpliced(i, 1) |
| Almashtirish | arr[i] = x |
arr.with(i, x) |
| Tartiblash | arr.sort(...) |
arr.toSorted(...) |
| Har birini o'zgartirish | forEach ichida yozish |
arr.map(...) |
Chap ustundagilar holat uchun xavfli — ular massivning o'zini o'zgartiradi. O'ng ustundagilar har doim yangi massiv qaytaradi.
Tekshirib ko'ring: Ro'yxatning o'rtasiga — 1-indeksga — yangi element qo'shish kerak. Jadvalda yo'q. Qaysi nusxa metodi bilan qilasiz?
Javob
arr.toSpliced(1, 0, x) — 1-indeksdan boshlab 0 ta element o'chiradi va x ni qo'yadi (Nusxa bilan o'zgartirish). Spread bilan ham bo'ladi: [...arr.slice(0, 1), x, ...arr.slice(1)] — lekin toSpliced niyatni aniqroq aytadi.
3.2 Massiv ichidagi obyekt
Eng ko'p uchraydigan holat: ro'yxatdagi bitta obyektni yangilash. Buyurtmada osh sonini bittaga oshiramiz. map darsidagi naqsh:
const taomlar = [
{ id: 1, nom: "Osh", soni: 1 },
{ id: 2, nom: "Ko'k choy", soni: 2 },
];
const yangi = taomlar.map((t) =>
t.id === 1 ? { ...t, soni: t.soni + 1 } : t,
);
console.log(yangi[0].soni, taomlar[0].soni); // 2 1
console.log(yangi[1] === taomlar[1]); // truemap har elementga qaraydi: kerakli id bo'lsa — yangi obyekt ({ ...t, soni: ... }), aks holda — o'sha obyekt. Choy o'zgarmadi — u ikkala massivda umumiy. Bu ham yo'l bo'ylab nusxa: massiv → bitta obyekt.
Indeks ma'lum bo'lsa, with qisqaroq:
const taomlar = [
{ id: 1, nom: "Osh", soni: 1 },
{ id: 2, nom: "Ko'k choy", soni: 2 },
];
const i = taomlar.findIndex((t) => t.id === 2);
const yangi = taomlar.with(i, { ...taomlar[i], soni: 3 });
console.log(yangi[1].soni, taomlar[1].soni); // 3 2Ikkalasi ham to'g'ri. map — "id bo'yicha" fikrlashga yaqin va element topilmasa ham xatosiz ishlaydi. with — indeks bilan, lekin findIndex -1 qaytarsa, with(-1, ...) oxirgi elementni almashtiradi (Nusxa bilan o'zgartirish) — bunday holatni oldindan tekshiring.
3.3 Uch qavat: massiv ichidagi obyekt ichidagi massiv
Har taomning qo'shimchalari bor. Oshga "qo'shimcha go'sht" qo'shamiz:
const taomlar = [
{ id: 1, nom: "Osh", qoshimcha: ["salat"] },
{ id: 2, nom: "Manti", qoshimcha: [] },
];
const yangi = taomlar.map((t) =>
t.id === 1 ? { ...t, qoshimcha: [...t.qoshimcha, "go'sht"] } : t,
);
console.log(yangi[0].qoshimcha); // [ 'salat', "go'sht" ]
console.log(taomlar[0].qoshimcha); // [ 'salat' ]Yo'l: massiv (map) → obyekt ({ ...t }) → ichki massiv ([...t.qoshimcha, ...]). Qoida o'sha: yo'ldagi har qavat — nusxa, qolgani — umumiy. Qavatlar ko'paygan sari kod uzayadi — buni «Real ishda» bo'limida eslaymiz.
Tekshirib ko'ring:
taomlar.map((t) => { t.soni++; return t; })— immutable yangilashmi? Natija yangi massivmi?
Javob
Massiv yangi, lekin obyektlar — eskilari, va ular o'zgartirildi (t.soni++). Bu immutable emas: asl taomlar dagi obyektlar ham buzildi. map o'zi xavfsiz, lekin callback ichida mutatsiya qilinsa — himoya yo'q (Callback imzosi darsidagi qoida). To'g'risi: (t) => ({ ...t, soni: t.soni + 1 }).
3.4 Lug'at ko'rinishidagi obyekt
reduce darsida ro'yxatni id bo'yicha "lug'at" ga aylantirgan edik: { 1: {...}, 3: {...} }. Bunday obyektda bitta yozuvni yangilash — kalit nomi o'zgaruvchida, shuning uchun computed kalit kerak:
const taomlar = {
1: { nom: "Osh", soni: 1 },
2: { nom: "Manti", soni: 2 },
};
const id = 2;
const yangi = { ...taomlar, [id]: { ...taomlar[id], soni: 3 } };
console.log(yangi[2].soni, taomlar[2].soni); // 3 2
console.log(yangi[1] === taomlar[1]); // true[id]: ... — kalit nomi id o'zgaruvchisidan olinadi. Yo'l: lug'at → bitta yozuv; yozuvdan tashqaridagi osh umumiy qoldi. Lug'atdan o'chirish ham nusxa bilan: obyekt destructuring dagi rest — const { [id]: _, ...qolgan } = taomlar; — _ — "bu qiymat kerak emas" degani uchun odatiy nom (o'chirilayotgan yozuv shunga tushadi). qolgan da id kaliti yo'q, asl lug'at esa joyida. Massiv bilanmi yoki lug'at bilanmi ishlash — vazifaga bog'liq; immutable yangilash qoidasi ikkalasida bir xil.
4. ?. — chuqur qiymatni xavfsiz o'qish
4.1 O'qishda
Ichma-ich ma'lumotda ba'zi qavatlar bo'lmasligi mumkin: mehmon manzilsiz bron qilgan. ?? va ?. darsidagi ?. chuqur o'qishni xavfsiz qiladi:
const bron = { mijoz: { ism: "Bobur" } };
const uy = bron.mijoz?.manzil?.uy ?? "ko'rsatilmagan";
console.log(uy); // ko'rsatilmaganbron.mijoz.manzil.uy — TypeError: Cannot read properties of undefined (reading 'uy') berardi. ?. esa manzil yo'qligini ko'rib, darhol undefined qaytardi, ?? uni tushunarli matnga aylantirdi.
Yangilashda ham bu muhim: yo'q qavatni spread qilish xato bermaydi. { ...undefined } — bo'sh obyekt. Shuning uchun manzili yo'q mehmonga uy raqamini qo'shish ham o'sha naqsh bilan ishlaydi:
const bron = { mijoz: { ism: "Bobur" } };
const yangi = {
...bron,
mijoz: { ...bron.mijoz, manzil: { ...bron.mijoz.manzil, uy: 3 } },
};
console.log(yangi); // { mijoz: { ism: 'Bobur', manzil: { uy: 3 } } }4.2 Tayinlashda — yo'q
?. faqat o'qish uchun. Uning chap tomonida tayinlash yozib bo'lmaydi:
const bron = { mijoz: null };
bron.mijoz?.ism = "Ali";SyntaxError: Invalid left-hand side in assignmentTarjimasi: "tayinlashning chap tomoni noto'g'ri". SyntaxError — kod hatto ishga tushmaydi (Stack trace va xato turlari). Mantiqi oddiy: bron.mijoz null bo'lsa, ism ni qayerga yozish kerak? JavaScript bu savolga javob bermaydi. Immutable usulda bu savol umuman tug'ilmaydi — biz yozmaymiz, yangi obyekt yasaymiz.
Tekshirib ko'ring:
const b = { mijoz: { ism: "Bobur" } };uchunb.mijoz?.manzil.uynima beradi??.qayerda turishi kerak?
Javob
TypeError: Cannot read properties of undefined (reading 'uy'). ?. faqat o'zidan oldingi qiymatni tekshiradi: b.mijoz bor, shuning uchun .manzil o'qildi — undefined. Keyingi .uy esa oddiy nuqta bilan — xato. To'g'risi: b.mijoz?.manzil?.uy — yo'q bo'lishi mumkin bo'lgan har qavatdan keyin ?..
5. Muzlatish bilan tekshirish
Immutable kod yozdingizmi yoki qayerdadir mutatsiya qolib ketdimi — buni qanday bilasiz? Obyektni himoyalash darsidagi chuqurMuzlat aynan shu ishga yaraydi. Holatni muzlatib, yangilash funksiyasini modulda ishga tushiramiz. Mutatsiya bo'lsa — darhol TypeError:
function chuqurMuzlat(obyekt) {
for (const qiymat of Object.values(obyekt)) {
if (typeof qiymat === "object" && qiymat !== null) {
chuqurMuzlat(qiymat);
}
}
return Object.freeze(obyekt);
}
const holat = chuqurMuzlat({
taomlar: [{ id: 1, nom: "Osh", soni: 1 }],
mijoz: { ism: "Malika" },
});
function soniniOshir(h, id) {
return {
...h,
taomlar: h.taomlar.map((t) =>
t.id === id ? { ...t, soni: t.soni + 1 } : t,
),
};
}
const keyin = soniniOshir(holat, 1);
console.log(keyin.taomlar[0].soni, holat.taomlar[0].soni); // 2 1
console.log(keyin.mijoz === holat.mijoz); // trueXato chiqmadi — demak soniniOshir muzlagan holatga bir marta ham yozmadi. Ikkinchi qatorga ham qarang: mijoz yo'ldan tashqarida edi va umumiy qoldi — muzlagan obyektni bo'lishish xavfsiz, chunki uni hech kim o'zgartira olmaydi. Agar ichida t.soni++ bo'lganida, modul TypeError: Cannot assign to read only property 'soni' bilan to'xtardi. Redux Toolkit (React ilovalarida holat boshqaradigan kutubxona — keyingi qismlarda) ishlab chiqish rejimida holatni aynan shu sababli muzlatadi.
Tekshirib ko'ring:
soniniOshirichidagih.taomlar.map(...)nih.taomlar.forEach((t) => { if (t.id === id) t.soni++; })ga almashtirsak, bu modulda nima bo'ladi?
Javob
TypeError: Cannot assign to read only property 'soni' of object '#<Object>'. forEach callback'i muzlagan taom obyektiga yozmoqchi bo'ldi. Muzlatish xatoni darhol ushladi — aynan shu uning vazifasi. Muzlatilmagan holatda esa bu kod "ishlardi" va asl holatni jimgina buzardi.
Diqqat: Oddiy
<script>da (modul emas) muzlagan obyektga yozish jim e'tiborsiz qoladi (Obyektni himoyalash). U yerda muzlatish ma'lumotni saqlaydi, lekin xatoni ko'rsatmaydi. «Vazifalar qadami» davazifalar.jsaynan shunday skript — shuning uchun u yerda xulqni qo'lda va sinov ro'yxati bilan tekshiramiz.
6. Ko'p uchraydigan xatolar
6.1 Faqat yuqori qavatni nusxalash
const yangi = { ...bron }; yangi.mijoz.ism = "..." — ichki obyekt umumiy, asl ham o'zgaradi. Tuzatish: yo'ldagi har qavatni spread qiling.
6.2 map ichida mutatsiya
arr.map((t) => { t.soni = 3; return t; }) — massiv yangi, obyektlar buzilgan. Tuzatish: (t) => ({ ...t, soni: 3 }).
6.3 sort, reverse, splice holatda
Ular massivning o'zini o'zgartiradi. Tuzatish: toSorted, toReversed, toSpliced, filter.
6.4 ?. tayinlashda
a?.b = 1 — SyntaxError: Invalid left-hand side in assignment. Tuzatish: avval qavat borligini tekshiring yoki immutable usulda yangi obyekt yasang.
6.5 with ga -1 indeks
findIndex topmasa -1 qaytaradi, with(-1, x) esa oxirgi elementni almashtiradi. Tuzatish: map + id taqqoslash yoki -1 ni oldindan tekshirish.
6.6 Meros xususiyatlar nusxaga tushmaydi
Spread faqat obyektning o'z xususiyatlarini ko'chiradi. Obyekt bo'ylab yurish darsidagi Object.create bilan yasalgan buyurtmani eslang — uning valyuta si "meros" edi:
const standart = { valyuta: "so'm" };
const buyurtma = Object.create(standart);
buyurtma.taom = "osh";
const nusxa = { ...buyurtma, soni: 2 };
console.log(buyurtma.valyuta, nusxa.valyuta); // so'm undefinedNusxa oddiy obyekt bo'lib qoldi — merosdan uzildi. Hozircha bu kam uchraydi. Lekin 10-qismning klasslar modulida vazifalar o'z metodlari bilan keladi (vazifa.almashtir() kabi), va metodlar aynan meros orqali ishlaydi (class sintaksisi). Bunday obyektni { ...vazifa } bilan nusxalasangiz, metodlar yo'qoladi. Tuzatish: oddiy ma'lumot obyektlarida spread — to'g'ri yo'l; metodli obyektlarda esa yangilash usulini o'sha obyekt o'zi beradi — buni klasslar darslarida ko'ramiz.
6.7 Spread tartibi adashgan
const manzil = { shahar: "Toshkent", uy: 7 };
const yangi = { uy: 12, ...manzil };
console.log(yangi.uy); // 7Yangi qiymat yozildi — va darhol eski uy uni bosib ketdi. Obyekt literalida keyingi kalit oldingisini almashtiradi (Spread va rest). Tuzatish: avval eskisi, keyin o'zgarish — { ...manzil, uy: 12 }. Qoida: spread — boshida, yangi qiymatlar — oxirida.
7. Mashqlar
1-mashq (oson): Sozlamadagi bitta qiymat
Muzlatilgan sozlamada faqat ovoz.balandlik ni 8 ga o'zgartiring. Asl obyekt va til umumiy qolishi kerak. Ishora: yo'l — sozlama → ovoz → balandlik.
const sozlama = Object.freeze({
til: "uz",
ovoz: Object.freeze({ yoqilgan: true, balandlik: 5 }),
});Yechim
const sozlama = Object.freeze({
til: "uz",
ovoz: Object.freeze({ yoqilgan: true, balandlik: 5 }),
});
const yangi = { ...sozlama, ovoz: { ...sozlama.ovoz, balandlik: 8 } };
console.log(yangi.ovoz.balandlik, sozlama.ovoz.balandlik); // 8 5
console.log(yangi.ovoz.yoqilgan); // trueIkki spread — yo'ldagi ikki obyekt. Asl muzlagan bo'lgani uchun, agar biz uni o'zgartirmoqchi bo'lsak, modul xato berardi — xato yo'q, demak hammasi to'g'ri.
2-mashq (o'rta): Savatdan o'chirish va qo'shish
Ikki funksiya yozing: taomQosh(savat, taom) va taomOchir(savat, id). Ikkalasi ham yangi savat qaytarsin, eskisiga tegmasin. Savat — { egasi, taomlar: [...] }. Ishora: [...arr, x] va filter.
Yechim
function taomQosh(savat, taom) {
return { ...savat, taomlar: [...savat.taomlar, taom] };
}
function taomOchir(savat, id) {
return {
...savat,
taomlar: savat.taomlar.filter((t) => t.id !== id),
};
}
const s0 = Object.freeze({
egasi: "Aziz",
taomlar: Object.freeze([]),
});
const s1 = taomQosh(s0, { id: 1, nom: "Osh" });
const s2 = taomQosh(s1, { id: 2, nom: "Manti" });
const s3 = taomOchir(s2, 1);
console.log(s0.taomlar.length, s1.taomlar.length, s2.taomlar.length);
console.log(s3.taomlar.map((t) => t.nom));Konsolda:
0 1 2
[ 'Manti' ]Har qadam yangi savat berdi, eskilari o'z holicha qoldi — s0 dan s3 gacha butun tarix saqlangan. "Bekor qilish" tugmasi uchun shu tarixning oldingi elementini olish kifoya.
3-mashq (qiyin): Yo'l bo'yicha yangilash funksiyasi
yolBilan(obyekt, yol, qiymat) funksiyasini yozing: yol — kalitlar massivi (["mijoz", "manzil", "uy"]). U yo'l bo'ylab nusxa yasab, oxirgi kalitga qiymat ni qo'ysin. Yo'ldagi qavat yo'q bo'lsa — bo'sh obyekt deb olsin. Ishora: rekursiya — birinchi kalitni oling, qolgan yo'l uchun funksiyani o'zi chaqirsin; massiv destructuring bilan [kalit, ...qolgan].
Yechim
function yolBilan(obyekt, yol, qiymat) {
if (yol.length === 0) {
return qiymat;
}
const [kalit, ...qolgan] = yol;
const ichki = obyekt?.[kalit];
return { ...obyekt, [kalit]: yolBilan(ichki, qolgan, qiymat) };
}
const bron = Object.freeze({
mijoz: Object.freeze({ ism: "Malika" }),
odam: 4,
});
const yangi = yolBilan(bron, ["mijoz", "manzil", "uy"], 12);
console.log(yangi);
console.log(bron.mijoz);Konsolda:
{ mijoz: { ism: 'Malika', manzil: { uy: 12 } }, odam: 4 }
{ ism: 'Malika' }To'xtash sharti — yo'l tugadi: endi qiymatning o'zi qaytadi. Har qadamda { ...obyekt, [kalit]: ... } — joriy qavatning nusxasi, bitta kalit almashtirilgan (Computed kalitlar). obyekt?.[kalit] — qavat yo'q bo'lsa (manzil), undefined beradi, keyingi qadamda { ...undefined } esa bo'sh obyekt. Bu funksiya Lodash kutubxonasidagi set ning immutable varianti — faqat massiv indekslarini emas, obyekt kalitlarini qo'llaydi.
4-mashq: Vazifalar qadami — immutable yangilash va modulni yakunlash
refactor/obyektlar branch'idagi oxirgi ish. Obyektni himoyalash darsida ko'rdik: holat.vazifalar ga muzlagan boshlangichVazifalar ni to'g'ridan-to'g'ri bersak, ilova jimgina buziladi — chunki uchta funksiya ma'lumotni hali ham joyida o'zgartiradi:
function vazifaQosh(matn) {
holat.vazifalar.push({
id: holat.keyingiId,
matn,
bajarildi: false,
});
// ...
}
function vazifaniAlmashtir(id) {
const vazifa = vazifaniTop(id);
vazifa.bajarildi = !vazifa.bajarildi;
render();
}
function matnniAlmashtir(id, matn) {
vazifaniTop(id).matn = matn;
// ...
}To'rtinchisi — vazifaniOchir — Nusxa bilan o'zgartirish darsidan beri toSpliced bilan yangi massiv beradi. Qolgan uchtasini immutable qiling: har biri holat.vazifalar ga yangi massiv bersin, vazifa obyektlariga tegmasin. So'ng nusxani olib tashlang — vazifalar: boshlangichVazifalar. Xulq o'zgarmasin.
Yechim
Bitta yordamchi funksiya — "id bo'yicha vazifani o'zgartirib, yangi massiv ber":
function vazifaniYangila(id, ozgarish) {
const orin = holat.vazifalar.findIndex((v) => v.id === id);
holat.vazifalar = holat.vazifalar.with(orin, {
...holat.vazifalar[orin],
...ozgarish,
});
}ozgarish — o'zgaradigan maydonlar obyekti: { bajarildi: true } yoki { matn: "..." }. Ikki spread: avval eski vazifa, keyin o'zgarish — keyingisi bir xil kalitni bosib ketadi. with — yangi massiv, orin dagi element almashtirilgan. Bu funksiya faqat mavjud id bilan chaqiriladi (tugmalar har doim mavjud vazifaga tegishli), shuning uchun -1 holati yo'q.
Uch funksiya endi shunday (vazifaniOchir ni ham qisqartirdik — pastda):
function vazifaQosh(matn) {
holat.vazifalar = [
...holat.vazifalar,
{ id: holat.keyingiId, matn, bajarildi: false },
];
holat.keyingiId++;
if (holat.filtr === "bajarilgan") {
holat.filtr = "hammasi";
}
render();
}
function vazifaniAlmashtir(id) {
const bajarildi = !vazifaniTop(id).bajarildi;
vazifaniYangila(id, { bajarildi });
render();
}
function vazifaniOchir(id) {
holat.vazifalar = holat.vazifalar.filter((v) => v.id !== id);
render();
}
function matnniAlmashtir(id, matn) {
vazifaniYangila(id, { matn });
holat.tahrirId = null;
render();
}Va holat boshida — nusxasiz:
const holat = {
vazifalar: boshlangichVazifalar,
// keyingiId, filtr, tahrirId va qoldi getter'i — avvalgidek
};E'tibor bering:
vazifaniOchir—filterbilan.toSplicedham immutable edi, lekin avvalfindIndexbilan indeks qidirardi.filter((v) => v.id !== id)indekssiz, bitta qatorda o'sha ishni qiladi (filterdarsidagi "xavfsiz o'chirish").holatning o'zi immutable emas.holat.vazifalar = ...,holat.filtr = ...—holatobyektiga yozamiz. Biz faqat vazifalar ma'lumotini (massiv va undagi obyektlarni) o'zgarmas qildik.holatni ham yangisiga almashtirish (holat = { ...holat, ... })qoldigetter'ini spread'da "qotirib" qo'yardi (Getter va setter) — bu ilova uchun hozircha kerak emas.- Muzlagan ma'lumot — sinov vositasi. Agar biror joyda mutatsiya qolib ketgan bo'lsa,
boshlangichVazifalarmuzlagani uchun o'sha amal ishlamay qoladi (yokipushxato beradi). Shuning uchun quyidagi ro'yxatning hammasi ishlashi — hamma mutatsiya olib tashlanganining isboti. - Bu — oddiy ma'lumot obyektlari uchun. Klasslar modulida vazifa
Vazifaklassining nusxasiga aylanadi va o'z metodlari bilan o'zgaradi. O'shanda bu yerdagi spread usuli to'g'ridan-to'g'ri ko'chmaydi — «Meros xususiyatlar nusxaga tushmaydi» bo'limidagi sabab bilan. Bugungi qoida esa qoladi: boshlang'ich ma'lumot buzilmasin. qoldigetter'i o'zgarmadi. U har o'qishdastatistika()orqaliholat.vazifalarni yangidan oladi — massiv yangisiga almashganini sezadi.
Eski va yangi funksiyalar bir xil natija berishini Node'da tekshiring — muzlagan boshlang'ich ma'lumot bilan:
const boshlangich = Object.freeze([
Object.freeze({ id: 1, matn: "Non olish", bajarildi: false }),
Object.freeze({ id: 2, matn: "Takrorlash", bajarildi: true }),
]);
function eskiAlmashtir(royxat, id) {
const nusxa = royxat.map((v) => ({ ...v }));
const vazifa = nusxa.find((v) => v.id === id);
vazifa.bajarildi = !vazifa.bajarildi;
return nusxa;
}
function yangiAlmashtir(royxat, id) {
const orin = royxat.findIndex((v) => v.id === id);
return royxat.with(orin, {
...royxat[orin],
bajarildi: !royxat[orin].bajarildi,
});
}
const holatga = (r) =>
r.map((v) => `${v.id}:${v.bajarildi}`).join(" ");
for (const id of [1, 2]) {
const a = eskiAlmashtir(boshlangich, id);
const b = yangiAlmashtir(boshlangich, id);
console.log(holatga(a), "|", holatga(b));
}
console.log(holatga(boshlangich));Konsolda:
1:true 2:true | 1:true 2:true
1:false 2:false | 1:false 2:false
1:false 2:trueHar qatorda ikki ustun bir xil, oxirgi qator esa — muzlagan boshlang'ich ma'lumot o'zgarmagan. Kod modulda ishladi va TypeError bermadi: yangiAlmashtir muzlagan obyektlarga bir marta ham yozmadi.
Brauzerda butun ilovani sinab chiqing — bu besh darsdagi refaktoringning yakuniy tekshiruvi:
- Ochilganda — uch vazifa, "2 ta vazifa qoldi, 1 ta bajarildi".
- "Non olish" ni belgilang — chizilib, "1 ta vazifa qoldi, 2 ta bajarildi"; qaytaring — yana "2 ta vazifa qoldi". (Nusxasiz tajribadagidek "katak qaytib ketmaydi".)
- Tahrirlab
Enter— matn haqiqatan almashadi, "O'zgarish saqlandi.", fokus «Tahrirlash» da.Esc— o'zgarishsiz qaytadi. - Yangi vazifa qo'shing — konsolda xato yo'q, "Bajarilgan" filtrida qo'shilsa, filtr «Hammasi» ga o'tadi.
- Hammasini o'chiring — "Hozircha vazifa yo'q. Birinchisini qo'shing!", "0 ta vazifa qoldi, 0 ta bajarildi", fokus "Ro'yxat" sarlavhasida.
- DevTools konsolida:
Object.isFrozen(boshlangichVazifalar[0])—true,boshlangichVazifalar[0].bajarildi— hamonfalse: ilova boshlang'ich ma'lumotga tegmadi.
Obyektlar modulida vazifalar.js da nima o'zgardi:
| Nima | Edi | Endi | Dars |
|---|---|---|---|
| Qolgan vazifalar soni | faqat statistika().qoldi |
holat.qoldi getter ham |
Getter va setter |
qoldi nusxalarda |
sanaladi | sanalmaydi | Deskriptorlar |
| Boshlang'ich ma'lumot, matnlar | oddiy obyekt | muzlagan (chuqurMuzlat) |
Obyektni himoyalash |
| Filtr nomi | tekshirilmaydi | Object.hasOwn |
Object.* metodlari |
| Vazifalarni o'zgartirish | joyida | yangi massiv | shu dars |
Commit va branch'ni yakunlash:
git add assets/js/vazifalar.js
git commit -m "Ro'yxat: vazifalarni immutable yangila"
git switch main
git merge refactor/obyektlar
git branch -d refactor/obyektlar
git pushPages "Deploy from a branch" rejimida — bir-ikki daqiqadan keyin sayt yangilanadi (Yakuniy loyiha). Telefonda oching: foydalanuvchi hech qanday farq ko'rmasligi kerak.
8. Real ishda
- React va Redux.
setState({ ...holat, mijoz: { ...holat.mijoz, ism } })vaitems.map((x) => x.id === id ? { ...x, ... } : x)— React kodida har kuni yoziladigan qatorlar. Redux reducer'lari ham faqat shunday yoziladi. React — 17-qismda. - Immer. Uch-to'rt qavatli spread o'qishga qiyin. Immer kutubxonasi "joyida o'zgartirgandek" yozishga ruxsat beradi, ichida esa aynan bugungi yo'l bo'ylab nusxani yasaydi. Uning g'oyasini Immutability chuqur darsida ko'ramiz.
- "Bekor qilish" va tarix. Matn muharrirlari, rasm dasturlari, forma "qoralamasi" — har holat alohida saqlanadi. Immutable yangilash buni arzon qiladi: o'zgarmagan qismlar umumiy.
- Intervyu. "Ichma-ich obyektni qanday immutable yangilaysiz?", "nega React'da holatni joyida o'zgartirib bo'lmaydi?", "
structuredClonebilan nima farqi bor?" — frontend intervyusining klassik savollari.
Xulosa
- Immutable yangilash — o'zgartirmaslik, o'zgargan yangi nusxa yasash; eski holat va tarix saqlanadi, o'zgarish
===bilan seziladi. - Ichma-ich obyektda yo'ldagi har obyekt spread bilan nusxalanadi; yo'ldan tashqaridagilar umumiy qoladi (structural sharing).
- Massivlarda: qo'shish —
[...arr, x], o'chirish —filter, almashtirish —mapyokiwith, tartiblash —toSorted. ?.chuqur o'qishni xavfsiz qiladi, lekin tayinlashning chap tomonida ishlamaydi.- Holatni muzlatib, modulda sinash — qolib ketgan mutatsiyani topishning ishonchli yo'li.
Keyingi dars: JSON asoslari — obyektni matnga aylantirib saqlash va boshqa dasturga yuborishni o'rganamiz.
Manbalar
- MDN: "Spread syntax", "Optional chaining", "Array.prototype.with()" — developer.mozilla.org
- React hujjatlari: "Updating Objects in State", "Updating Arrays in State" — react.dev
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!