Mundarija (33)
- Bu darsda
- 1. Nega bu kerak?
- 2. Imperativ va deklarativ
- 2.1 Bitta masala, ikki uslub
- 2.2 Farq qayerda
- 2.3 Deklarativlik — daraja
- 3. FP'ning asosiy g'oyalari
- 3.1 Funksiya — oddiy qiymat
- 3.2 Toza funksiya va havola shaffofligi
- 3.3 Ma'lumot o'zgartirilmaydi
- 3.4 Ma'lumot oqimi
- 3.5 Ifoda — ko'rsatmadan afzal
- 4. Deklarativ vositalar qutisi
- 4.1 Deklarativ va erta chiqish
- 5. Funksional yadro, imperativ qobiq
- 5.1 Yon ta'sirsiz dastur bo'lmaydi
- 5.2 «Bahor» hisoboti ikki qatlamda
- 6. FP qayerdan keldi va qayerga boradi
- 7. Qachon imperativ yaxshiroq
- 8. Ko'p uchraydigan xatolar
- 8.1 Zanjir ichida yon ta'sir
- 8.2 map ichida return yo'q
- 8.3 Zanjirda sort
- 8.4 Toza funksiya ichida joriy vaqt
- 8.5 FP'ni maqsadga aylantirish
- 9. Mashqlar
- 1-mashq (oson): Imperativdan deklarativga
- 2-mashq (o'rta): Yadro va qobiqni ajrating
- 3-mashq (qiyin): Eng faol mehmonlar
- 4-mashq: Vazifalar qadami — statistika toza yadroda
- 10. Real ishda
- Xulosa
- Manbalar
JavaScript'da funksional dasturlash: FP g'oyalari va deklarativ uslub
Qisqacha: Imperativ kod kompyuterga qanday qilishni qadamma-qadam aytadi: sikl, hisoblagich,
if, o'zgaruvchini yangilash. Deklarativ kod esa nima kerakligini aytadi:buyurtmalar.filter(tolanganmi).map(summasi). Funksional dasturlash (FP) deklarativ uslubni bir necha g'oyaga quradi: funksiya — oddiy qiymat, funksiyalar toza, ma'lumot o'zgartirilmaydi, dastur — ma'lumot oqib o'tadigan bosqichlar zanjiri. Yon ta'sirlar esa dasturning chetiga — "qobiq"qa chiqariladi.
Bu darsda
- Bitta masalani imperativ va deklarativ uslubda yozib, farqini tushuntira olasiz.
- FP'ning asosiy g'oyalarini aytib bera olasiz: birinchi darajali funksiya, toza funksiya, havola shaffofligi, o'zgarmaslik, ma'lumot oqimi.
- Kodni "funksional yadro" va "imperativ qobiq"qa ajrata olasiz.
- Deklarativ kod qachon yaxshi va qachon oddiy sikl yaxshiroq ekanini bilasiz.
Oldin bilishingiz kerak: Pure funksiya va side effect, Metod zanjiri va ma'lumot pipeline, Closure amaliyotda, reduce.
1. Nega bu kerak?
Oldingi darsda eval va new Function bilan satrni kodga aylantirishning xavfini ko'rdik. Bugun boshqa savol: kodni qanday uslubda yozish kerak, toki u tushunarli va kam xatoli bo'lsin?
Sardor «Bahor» uchun kunlik hisobot yozdi. Unga uch narsa kerak edi: to'langan buyurtmalar tushumi, eng ko'p sotilgan taom va to'lanmagan buyurtmalar soni. Uning kodi bitta katta sikl edi: beshta let, uchta if, ichma-ich hisoblagichlar. Ishladi. Bir hafta o'tib administrator so'radi: "Choyni tushumdan chiqarib qo'y". Sardor bitta if qo'shdi — va to'lanmaganlar soni ham buzildi, chunki ikkalasi bitta siklda, bitta o'zgaruvchilar bilan bog'langan edi.
Muammo sikl qanday yozilganida emas. Muammo shundaki, uch xil savolning javobi bitta "qozon"da pishirildi. Bitta ingredientni o'zgartirsangiz — butun ovqat o'zgaradi.
Dasturchilar bunday kodni boshqacha yozishni o'ylab topishgan: har savol — alohida kichik funksiya, ma'lumot ular orqali "oqib" o'tadi, hech kim hech narsani yo'l-yo'lakay o'zgartirmaydi. Bu yondashuv funksional dasturlash deb ataladi. Siz uning qismlarini allaqachon bilasiz: toza funksiyalar (Pure funksiya), filter/map/reduce (Metod zanjiri), closure (Closure amaliyotda). Bugun ularni bitta tizimga yig'amiz.
Hayotdan o'xshatish: taksi. Haydovchiga ikki xil aytish mumkin. Birinchisi: "To'g'riga 300 metr, svetoforda chapga, ikkinchi burilishda o'ngga, ko'k darvoza oldida to'xta". Ikkinchisi: "Chilonzor, 9-kvartal, 12-uy". Birinchisi — imperativ (qanday borish). Ikkinchisi — deklarativ (qayerga kerak). Ikkinchisida yo'lni haydovchi va navigator o'zi topadi.
2. Imperativ va deklarativ
2.1 Bitta masala, ikki uslub
«Bahor»ning bugungi buyurtmalari:
const buyurtmalar = [
{ id: 1, taom: "Osh", narx: 35000, soni: 2, tolandi: true },
{ id: 2, taom: "Manti", narx: 30000, soni: 1, tolandi: false },
{ id: 3, taom: "Osh", narx: 35000, soni: 1, tolandi: true },
{ id: 4, taom: "Ko'k choy", narx: 5000, soni: 3, tolandi: true },
];
let tushum = 0;
for (let i = 0; i < buyurtmalar.length; i++) {
const buyurtma = buyurtmalar[i];
if (buyurtma.tolandi) {
tushum = tushum + buyurtma.narx * buyurtma.soni;
}
}
console.log(tushum); // 120000Bu — imperativ uslub (imperative style): dastur kompyuterga buyruqlar ketma-ketligini beradi. "Hisoblagichni 0 qil. Indeksni 0 dan boshla. Indeks uzunlikdan kichik ekan — elementni ol. To'langan bo'lsa — tushumga qo'sh. Indeksni bittaga oshir." Natijani bilish uchun bu qadamlarni boshingizda birma-bir "bajarib ko'rish" kerak.
Endi xuddi shu hisob deklarativ uslubda:
const tolanganmi = (buyurtma) => buyurtma.tolandi;
const summasi = (buyurtma) => buyurtma.narx * buyurtma.soni;
const qoshish = (a, b) => a + b;
const tushum2 = buyurtmalar
.filter(tolanganmi)
.map(summasi)
.reduce(qoshish, 0);
console.log(tushum2); // 120000Bu — deklarativ uslub (declarative style): kod natija nima ekanini tasvirlaydi. Ovoz chiqarib o'qing: "buyurtmalardan to'langanlarini ol, har birining summasini top, hammasini qo'sh". Indeks yo'q, hisoblagich yo'q, let yo'q. Sikl ham bor, albatta — lekin u filter, map va reduce ning ichida yashiringan.
2.2 Farq qayerda
Ikki variantni uch savol bilan solishtiramiz:
| Savol | Imperativ | Deklarativ |
|---|---|---|
| Nechta o'zgaruvchi o'zgaradi? | tushum, i |
hech biri |
| Kodni o'qish uchun nimani kuzatish kerak? | har qadamdagi holat | bosqichlar nomini |
| Yangi shart qo'shish | siklning ichiga if |
zanjirga bitta bosqich |
Eng muhimi — uchinchi qator. Administrator "choysiz tushum" so'rasa, deklarativ variantga bitta qator qo'shiladi:
const choyEmasmi = (buyurtma) => buyurtma.taom !== "Ko'k choy";
const choysizTushum = buyurtmalar
.filter(tolanganmi)
.filter(choyEmasmi)
.map(summasi)
.reduce(qoshish, 0);
console.log(choysizTushum); // 105000Mavjud bosqichlarning hech biriga tegmadik — yangisini qo'shdik. tolanganmi, summasi, qoshish — kichik, nomli, qayta ishlatiladigan bo'laklar. Ular Sardorning boshqa hisobotlarida ham ishlaydi.
2.3 Deklarativlik — daraja
Siz deklarativ tillarni allaqachon bilasiz, faqat bu nom bilan emas:
- HTML:
<button>— "bu yerda tugma bo'lsin". Uni piksellab qanday chizishni brauzer o'zi hal qiladi. - CSS:
display: grid— "elementlar to'rda tursin". Har elementning koordinatasini siz hisoblamaysiz. - Regex:
/\d+/g— "sonlarni top". Belgilarni birma-bir tekshiruvchi siklni regex dvigateli yozadi (RegExp asoslari). - SQL:
SELECT ... WHERE tolandi— bazaga "nima kerak"ligini aytasiz; qanday qidirishni baza tanlaydi.
JavaScript ikkalasini ham qila oladi. Deklarativlik — "ha yoki yo'q" emas, daraja: siz qanchalik "qanday"ni yashirib, "nima"ni ko'rsatsangiz, kod shunchalik deklarativ.
Tekshirib ko'ring:
const katta = narxlar.some((n) => n > 50000);— imperativmi yoki deklarativmi? Uni imperativ yozish uchun nima kerak bo'lardi?
Javob
Deklarativ: "birortasi 50 000 dan kattami?" degan savol to'g'ridan-to'g'ri yozilgan. Imperativ variant uchun let katta = false, for sikli, ichida if va topilganda katta = true; break; kerak bo'lardi — besh-olti qator va o'zgaruvchan holat.
3. FP'ning asosiy g'oyalari
Funksional dasturlash (functional programming, FP) — dasturni toza funksiyalar va ularning birikmasidan quradigan paradigma (dastur yozish uslubi va qarashlar tizimi). Boshqa paradigmalarni ham ko'rgansiz: imperativ (sikl va buyruqlar) va obyektga yo'naltirilgan dasturlash — ma'lumot va uning metodlarini klasslarga yig'ish (class sintaksisi). JavaScript ko'p paradigmali til: bitta faylda uchalasini ham ishlatish mumkin. FP — ulardan biri, qurol qutisidagi yana bir asbob.
FP beshta g'oyaga tayanadi. Hammasini qisman bilasiz — endi ularni yonma-yon qo'yamiz.
3.1 Funksiya — oddiy qiymat
Funksiya — qiymat darsidan bilasiz: JavaScript'da funksiyani o'zgaruvchiga saqlash, argument qilib berish va qaytarish mumkin. Bu xususiyat birinchi darajali funksiya (first-class function) deb ataladi. FP aynan shunga quriladi: filter(tolanganmi) — funksiyani boshqa funksiyaga berish.
Funksiyalarni hatto ma'lumot kabi ro'yxatga solish mumkin. Masalan, buyurtmani tekshiradigan qoidalar:
const qoidalar = [
(b) => b.soni > 0 || "soni musbat bo'lsin",
(b) => b.narx >= 1000 || "narx juda kichik",
(b) => typeof b.taom === "string" || "taom nomi yo'q",
];
function xatolarniTop(buyurtma) {
return qoidalar
.map((qoida) => qoida(buyurtma))
.filter((natija) => natija !== true);
}
const togri = { taom: "Osh", narx: 35000, soni: 2 };
const notogri = { taom: "Osh", narx: 500, soni: 0 };
console.log(xatolarniTop(togri)); // []
console.log(xatolarniTop(notogri));Konsolda:
[]
[ "soni musbat bo'lsin", 'narx juda kichik' ]Har qoida — kichik funksiya: to'g'ri bo'lsa true, aks holda xato matni. || operatori chap tomon yolg'on bo'lsa o'ng tomonni qaytaradi (Mantiqiy operatorlar). Yangi qoida — massivga yangi qator, xatolarniTop ga tegilmaydi.
Funksiya qaytaradigan funksiya ham shu g'oyaning qismi. Closure amaliyotda darsidagi fabrika funksiyalarni eslang:
const chegirmaQil = (foiz) => (narx) => narx - (narx * foiz) / 100;
const talabaChegirmasi = chegirmaQil(10);
console.log([35000, 30000].map(talabaChegirmasi)); // [ 31500, 27000 ]chegirmaQil(10) yangi funksiya qaytardi va u to'g'ridan-to'g'ri map ga ketdi. Bunday "funksiya yasaydigan funksiyalar" — Currying darsining mavzusi.
3.2 Toza funksiya va havola shaffofligi
Pure funksiya darsidan ikki qoida: bir xil kirish — doim bir xil natija; tashqi dunyoni o'zgartirmaydi. Bu ikki qoidadan muhim natija kelib chiqadi.
summasi({ narx: 35000, soni: 2 }) doim 70000 qaytaradi va hech narsani o'zgartirmaydi. Demak, kodda bu chaqiruvni to'g'ridan-to'g'ri 70000 bilan almashtirsangiz, dastur xulqi o'zgarmaydi. Bu xususiyat havola shaffofligi (referential transparency) deb ataladi: ifoda o'z qiymati bilan almashtirilishi mumkin. Xuddi narxlar jadvali kabi: "osh narxi" o'rniga "35 000" deb yozsangiz, hisob o'zgarmaydi — chunki jadval har so'raganda bir xil javob beradi.
let hisoblagich = 0;
const summasi = (b) => b.narx * b.soni;
const sanabSummasi = (b) => {
hisoblagich++;
return b.narx * b.soni;
};
const osh = { narx: 35000, soni: 2 };
console.log(summasi(osh) + summasi(osh)); // 140000
const ikkalasi = sanabSummasi(osh) + sanabSummasi(osh);
console.log(ikkalasi, hisoblagich); // 140000 2summasi(osh) + summasi(osh) ni 70000 + 70000 deb yozsak — hech narsa o'zgarmaydi. sanabSummasi esa natijada xuddi shu sonni beradi, lekin har chaqiruvda hisoblagich ni o'zgartiradi. Uni 70000 bilan almashtirsak, hisoblagich 0 bo'lib qoladi — xulq o'zgardi. Demak, u shaffof emas.
Nega bu muhim? Havola shaffof kodni qismlarga bo'lib o'qish mumkin. Har funksiyani alohida tushunasiz, alohida sinaysiz, natijasini eslab qolasiz (Memoization aynan shunga tayanadi). Shaffof bo'lmagan kodda esa har chaqiruv "yana nimani o'zgartirdi?" degan savol tug'diradi.
3.3 Ma'lumot o'zgartirilmaydi
FP'da funksiya kirgan ma'lumotni o'zgartirmaydi — yangisini qaytaradi. filter, map, toSorted (Nusxa bilan o'zgartirish) aynan shunday ishlaydi: asl massiv joyida qoladi. sort, push, splice esa asl massivni o'zgartiradi — FP uslubidagi zanjirga ular to'g'ri kelmaydi.
Bu g'oya shunchalik muhimki, unga keyingi butun dars bag'ishlangan — Immutability chuqur. Hozircha qoida: zanjir ichida hech narsani joyida o'zgartirmang.
3.4 Ma'lumot oqimi
Imperativ kodda ma'lumot bir joyda "turadi" va uni ko'p qadam o'zgartiradi. FP'da esa ma'lumot bosqichlardan oqib o'tadi: har bosqich kirishni oladi va yangi chiqish beradi. Bu ma'lumot oqimi (data flow) — Metod zanjiri darsidagi pipeline'ning umumiy nomi.
Sxemada tushum hisobi qanday oqishini kuzating. Har strelka ustida — o'sha joydagi qiymat:
flowchart LR
A["buyurtmalar<br/>4 ta"] -->|"filter(tolanganmi)"| B["3 ta to'langan"]
B -->|"map(summasi)"| C["70000, 35000, 15000"]
C -->|"reduce(qoshish, 0)"| D["tushum<br/>120000"]Har to'rtburchak — alohida qiymat, hech biri boshqasini buzmaydi. Xato chiqsa, u qaysi bosqichda ekanini topish oson: har oraliq natijani console.log bilan ko'rish mumkin. Bosqichlarni ulashning umumiy vositasi — pipe — Kompozitsiya darsida.
3.5 Ifoda — ko'rsatmadan afzal
Imperativ kod ko'pincha ko'rsatmalar (statements) bilan yoziladi: "o'zgaruvchi e'lon qil, keyin shartga qarab unga qiymat ber". FP esa ifodalarni afzal ko'radi: qiymatni darhol, bir joyda hisoblaydigan yozuv.
const buyurtma = { tolandi: false };
let holat1;
if (buyurtma.tolandi) {
holat1 = "to'langan";
} else {
holat1 = "kutilmoqda";
}
const holat2 = buyurtma.tolandi ? "to'langan" : "kutilmoqda";
console.log(holat1, holat2); // kutilmoqda kutilmoqdaIkkinchi variantda holat2 — const, u bir marta, e'lon qilingan joyda qiymat oladi. Birinchisida holat1 bir muddat undefined bo'lib turadi va keyin o'zgaradi. Kichik farq, lekin katta kodda let qancha kam bo'lsa, "bu o'zgaruvchi hozir nimaga teng?" degan savol shuncha kam.
Ko'p shoxli switch ham ko'pincha qidiruv obyekti bilan almashtiriladi (switch va uning muqobillari):
const holatMatni = {
yangi: "Qabul qilindi",
tayyor: "Tayyor, olib keting",
berildi: "Yoqimli ishtaha!",
};
console.log(holatMatni["tayyor"]); // Tayyor, olib keting
const nomalum = holatMatni["bekor"] ?? "Holat aniq emas";
console.log(nomalum); // Holat aniq emasBu ham deklarativ: "holat → matn" moslik ma'lumot sifatida yozilgan, shartlar ketma-ketligi sifatida emas.
Tekshirib ko'ring:
const narxlar = []; for (const b of buyurtmalar) narxlar.push(b.narx);— buni bitta ifoda bilan qanday yozasiz? Natijada qaysi FP g'oyalari ishladi?
Javob
const narxlar = buyurtmalar.map((b) => b.narx);. Bo'sh massivni o'zgartirib to'ldirish (push) o'rniga, yangi massiv bitta ifodada hosil bo'ldi (o'zgarmaslik, ifoda). map ga funksiya berildi (birinchi darajali funksiya), va u toza: tashqarida hech narsani o'zgartirmaydi.
4. Deklarativ vositalar qutisi
Oldingi qismlarda o'rgangan ko'p vositalar aslida deklarativ. Ularni bitta jadvalda eslaymiz — har biri "qanday" sikl yozishdan ozod qiladi:
| Savol | Vosita |
|---|---|
| Shartga mos elementlar | filter, find, some, every |
| Har elementni o'zgartirish | map, flatMap |
| Bitta qiymatga yig'ish | reduce |
| Guruhlarga ajratish | Object.groupBy, Map.groupBy |
| Saralangan nusxa | toSorted |
| Takrorsiz to'plam | new Set(...), Set metodlari |
| Obyekt ↔ juftliklar | Object.entries, Object.fromEntries |
Ular birga qanday ishlashini ko'raylik. Administrator so'raydi: "Har taom bo'yicha to'langan tushum, kattasidan boshlab":
const buyurtmalar = [
{ taom: "Osh", narx: 35000, soni: 2, tolandi: true },
{ taom: "Manti", narx: 30000, soni: 1, tolandi: false },
{ taom: "Osh", narx: 35000, soni: 1, tolandi: true },
{ taom: "Ko'k choy", narx: 5000, soni: 3, tolandi: true },
];
const summasi = (b) => b.narx * b.soni;
const qoshish = (a, b) => a + b;
const tolanganlar = buyurtmalar.filter((b) => b.tolandi);
const guruhlar = Object.groupBy(tolanganlar, (b) => b.taom);
const hisobot = Object.entries(guruhlar)
.map(([taom, royxat]) => ({
taom,
tushum: royxat.map(summasi).reduce(qoshish, 0),
}))
.toSorted((a, b) => b.tushum - a.tushum);
console.log(hisobot);Konsolda:
[
{ taom: 'Osh', tushum: 105000 },
{ taom: "Ko'k choy", tushum: 15000 }
]Object.groupBy buyurtmalarni taom nomi bo'yicha guruhladi. Object.entries guruhlar obyektini [taom, royxat] juftliklariga aylantirdi. map har juftlikdan kichik hisobot obyekti yasadi, toSorted — tushum bo'yicha kamayish tartibida saraladi. Bitta ham let, bitta ham push yo'q.
4.1 Deklarativ va erta chiqish
Metod zanjiri darsida zanjirning kamchiligini ko'rgan edik: u to'xtay olmaydi — "uchtasi topildi, yetadi" deya olmaydi. Har bosqich butun massivni aylanadi. O'sha darsning javobi — oddiy sikl edi.
ES2025 bu muammoni deklarativ yo'l bilan ham hal qildi: iterator helpers. Ular dangasa ishlaydi — har elementni zanjirning oxirigacha olib boradi va kerakli miqdor yig'ilishi bilan to'xtaydi:
const buyurtmalar = [
{ id: 1, narx: 35000 },
{ id: 2, narx: 5000 },
{ id: 3, narx: 30000 },
{ id: 4, narx: 28000 },
];
let tekshirildi = 0;
const ikkitaQimmat = buyurtmalar
.values()
.filter((b) => {
tekshirildi++;
return b.narx > 20000;
})
.take(2)
.map((b) => b.id)
.toArray();
console.log(ikkitaQimmat, tekshirildi); // [ 1, 3 ] 3Ikkita qimmat buyurtma topilishi bilan zanjir to'xtadi — to'rtinchisi tekshirilmadi ham. Kod esa hamon "nima kerak" tilida: "qiymatlardan qimmatlarini ol, ikkitasini, id'larini".
Diqqat:
filterichidagitekshirildi++— yon ta'sir. Biz uni faqat nechta element tekshirilganini ko'rsatish uchun qo'ydik. Haqiqiy kodda deklarativ zanjir ichida tashqi o'zgaruvchini o'zgartirmang — bu «Ko'p uchraydigan xatolar» bo'limidagi birinchi xato.
5. Funksional yadro, imperativ qobiq
5.1 Yon ta'sirsiz dastur bo'lmaydi
Pure funksiya darsida "yon ta'sirsiz dastur foydasiz" degan edik. Ekranga chiqarish, faylga yozish, serverga so'rov, joriy vaqtni olish — hammasi yon ta'sir yoki toza bo'lmagan amal. Ularsiz dastur hech kimga hech narsa ko'rsata olmaydi.
FP ularni yo'q qilmaydi — chetga suradi. Dastur ikki qatlamga bo'linadi:
- Funksional yadro (functional core) — barcha hisob-kitob: toza funksiyalar. Ma'lumot kiradi, natija chiqadi.
- Imperativ qobiq (imperative shell) — yupqa tashqi qatlam: ma'lumotni oladi (fayl, tarmoq, vaqt), yadroga beradi, natijani chiqaradi.
Bu nom 2012-yilda Gary Bernhardt'ning ma'ruzasidan tarqalgan. Hayotdan o'xshatish: oshxona. Oshpaz (yadro) faqat pishiradi — masalliq kelsa, ovqat chiqaradi. U bozorga bormaydi va mehmonga o'zi olib chiqmaydi. Bozorga borish va dasturxonga tortish (qobiq) — boshqa odamlarning ishi. Shuning uchun oshpazning ishini tekshirish oson: bir xil masalliq — bir xil osh.
5.2 «Bahor» hisoboti ikki qatlamda
Sardorning kunlik hisobotini qayta quramiz. Sana ham, buyurtmalar ham yadroga parametr bo'lib kiradi — yadro ularni o'zi olmaydi:
// ===== Funksional yadro: faqat toza funksiyalar =====
const summasi = (b) => b.narx * b.soni;
const qoshish = (a, b) => a + b;
function hisobotYasa(buyurtmalar, sana) {
const tolanganlar = buyurtmalar.filter((b) => b.tolandi);
return {
sana: sana.toISOString().slice(0, 10),
tushum: tolanganlar.map(summasi).reduce(qoshish, 0),
tolanmagan: buyurtmalar.length - tolanganlar.length,
};
}
function hisobotMatni(hisobot) {
return [
`Sana: ${hisobot.sana}`,
`Tushum: ${hisobot.tushum} so'm`,
`To'lanmagan: ${hisobot.tolanmagan} ta`,
].join("\n");
}
// ===== Imperativ qobiq: tashqi dunyo bilan aloqa =====
const bugungi = [
{ taom: "Osh", narx: 35000, soni: 2, tolandi: true },
{ taom: "Manti", narx: 30000, soni: 1, tolandi: false },
];
const sana = new Date("2026-10-05T12:00:00Z");
console.log(hisobotMatni(hisobotYasa(bugungi, sana)));Konsolda:
Sana: 2026-10-05
Tushum: 70000 so'm
To'lanmagan: 1 taYadrodagi ikki funksiyada console.log ham, new Date() ham yo'q. Shuning uchun ularni istalgan sana va istalgan ro'yxat bilan, istalgan marta chaqirish mumkin — natija oldindan ma'lum. Haqiqiy dasturda qobiq new Date() ni (joriy vaqt) va bazadan buyurtmalarni oladi. Misolda esa aniq sana yozdik — natija har kompyuterda bir xil chiqishi uchun.
Sxemada ikki qatlam:
flowchart LR
subgraph Q["Imperativ qobiq"]
K["kirish: baza, vaqt"]
C["chiqish: konsol, ekran"]
end
subgraph Y["Funksional yadro"]
H["hisobotYasa"] --> M["hisobotMatni"]
end
K --> H
M --> CStrelkalar faqat bir yo'nalishda: qobiq yadroga ma'lumot beradi va natijani oladi. Yadro qobiqni chaqirmaydi.
Bu tuzilmaning eng katta foydasi — testlash. Yadroni sinash uchun server ham, brauzer ham kerak emas: kirish beriladi, chiqish solishtiriladi. Buni Birinchi avtomatik test darsida amalda qilasiz. React ham shu g'oyaga qurilgan: komponent — holatdan UI yasaydigan funksiya, ekranga chizish esa React'ning "qobig'i". React'ni keyingi qismlarda o'rganamiz, hozir bilish shart emas.
6. FP qayerdan keldi va qayerga boradi
FP g'oyalari kompyuterlardan ham eski. 1930-yillarda matematik Alonzo Church funksiyalar haqidagi nazariyani — lambda hisobini — yaratdi. 1958-yilda Lisp tili shu g'oyani dasturlashga olib kirdi. Keyinroq ML, Haskell, Erlang, Clojure, Elm kabi tillar paydo bo'ldi.
JavaScript'ning ijodkori Brendan Eich tilni 1995-yilda yaratayotganda Scheme (Lisp oilasidan) tilidan ilhomlangan. Shuning uchun JavaScript'da funksiyalar boshidanoq birinchi darajali bo'lgan va closure'lar bor. Bugun FP g'oyalari JavaScript ekotizimining hamma joyida:
- React — "UI = holatdan funksiya" g'oyasi, toza komponentlar, holatni o'zgartirmasdan yangilash;
- Redux va
useReducer— reducer: (eski holat, amal) → yangi holat (immutable yangilash darsida ko'rgansiz); - Ramda (0.32 versiya) va lodash/fp — FP uslubidagi tayyor funksiyalar kutubxonalari.
Kutubxonalarni hozir bilish shart emas. Muhimi: FP — "faqat FP" degan dogma emas. Real JavaScript kodida klasslar, sikllar va FP yonma-yon yashaydi. Har vazifaga mos asbob tanlanadi.
Keyingi to'rt dars FP qurollarini birma-bir ochadi:
- Kompozitsiya — funksiyalarni ulash;
- Currying — funksiyani bosqichma-bosqich chaqirish;
- Memoization — natijani eslab qolish;
- Xato va bo'sh qiymatlar bilan FP uslubida ishlash —
nullva xatolarni zanjirda boshqarish.
7. Qachon imperativ yaxshiroq
Halol bo'lish kerak: deklarativ kod har doim ham yaxshi emas.
- Juda katta ma'lumot va tezlik muhim. Zanjirning har bosqichi yangi massiv yasaydi. Million element ustida oddiy sikl bir necha barobar tezroq (Metod zanjiri darsidagi o'lchov). Iterator helpers bu farqni kamaytiradi, lekin yo'q qilmaydi.
- Murakkab qadamli mantiq. Holatlar mashinasi, qayta urinishlar, ko'p shartli
break— ba'zan sikl o'qishga osonroq. - Haddan oshgan "aqlli" zanjir. O'nta bosqichli, ichida ichma-ich
reducebo'lgan bir qator — deklarativ, lekin tushunarsiz. Maqsad — o'qiladigan kod. Agar sikl aniqroq bo'lsa — sikl yozing.
Qoida: avval deklarativ yozing. O'lchov tezlik muammosini ko'rsatsa yoki kod tushunarsiz bo'lib qolsa — o'sha joyni imperativ qayta yozing va izoh qoldiring.
8. Ko'p uchraydigan xatolar
8.1 Zanjir ichida yon ta'sir
const taomlar = ["Osh", "Manti"];
const nusxa = [];
const katta = taomlar.map((taom) => {
nusxa.push(taom);
return taom.toUpperCase();
});
console.log(katta, nusxa); // [ 'OSH', 'MANTI' ] [ 'Osh', 'Manti' ]Xato chiqmaydi, lekin map endi toza emas: u tashqi nusxa ni o'zgartiradi. Kod "deklarativ" ko'rinadi, aslida imperativ. Bunday yashirin o'zgarish keyin kutilmagan joyda paydo bo'ladi. Tuzatish: map faqat yangi qiymat qaytarsin. Yon ta'sir kerak bo'lsa — uni alohida, ochiq yozing (for...of yoki forEach).
8.2 map ichida return yo'q
const narxlar = [35000, 30000];
const yangi = narxlar.map((n) => { n * 2; });
console.log(yangi); // [ undefined, undefined ]Figurali qavsli arrow — tanali funksiya, unda return shart (Arrow funksiyalar). Tuzatish: (n) => n * 2 yoki { return n * 2; }.
8.3 Zanjirda sort
buyurtmalar.sort(...) asl massivni o'zgartiradi — boshqa joyda shu massivdan foydalanayotgan kod kutilmagan tartibni ko'radi. Tuzatish: toSorted — u nusxa qaytaradi.
8.4 Toza funksiya ichida joriy vaqt
hisobotYasa ichida new Date() yozilsa, u har chaqiruvda boshqa natija beradi va uni sinab bo'lmaydi. Tuzatish: vaqtni parametr qilib bering — qobiq uni oladi, yadro ishlatadi.
8.5 FP'ni maqsadga aylantirish
"Bitta ham for bo'lmasin" degan qoida bilan yozilgan kod ko'pincha o'qilmaydi. Tuzatish: FP — vosita. Kod o'qiladigan va to'g'ri bo'lsin — uslub ikkinchi darajali.
9. Mashqlar
1-mashq (oson): Imperativdan deklarativga
Quyidagi kodni filter va map bilan qayta yozing. let, for va push qolmasin.
const taomlar = [
{ nom: "Osh", narx: 35000 },
{ nom: "Ko'k choy", narx: 5000 },
{ nom: "Manti", narx: 30000 },
];
const qimmatlar = [];
for (let i = 0; i < taomlar.length; i++) {
if (taomlar[i].narx >= 30000) {
qimmatlar.push(taomlar[i].nom);
}
}
console.log(qimmatlar); // [ 'Osh', 'Manti' ]Yechim
const taomlar = [
{ nom: "Osh", narx: 35000 },
{ nom: "Ko'k choy", narx: 5000 },
{ nom: "Manti", narx: 30000 },
];
const qimmatmi = (taom) => taom.narx >= 30000;
const nomi = (taom) => taom.nom;
const qimmatlar = taomlar.filter(qimmatmi).map(nomi);
console.log(qimmatlar); // [ 'Osh', 'Manti' ]Ikki kichik nomli funksiya kodni gapga aylantirdi: "taomlardan qimmatlarini ol, nomlarini ol". qimmatmi — predikat: nomi "-mi" bilan tugaydi va true/false qaytaradi.
2-mashq (o'rta): Yadro va qobiqni ajrating
Bu funksiya bir vaqtda hisoblaydi, vaqtni oladi va konsolga yozadi. Uni ikkiga bo'ling: toza bronHolati(bron, hozir) — "o'tgan", "bugun" yoki "kelajakda" qaytarsin; qobiq esa natijani chiqarsin. Ishora: sanalarni toISOString().slice(0, 10) bilan solishtiring — satrlar "YYYY-MM-DD" shaklida bo'lgani uchun < to'g'ri ishlaydi.
function bronniKorsat(bron) {
const bugun = new Date().toISOString().slice(0, 10);
if (bron.sana < bugun) console.log(`${bron.ism}: o'tgan`);
else if (bron.sana === bugun) console.log(`${bron.ism}: bugun`);
else console.log(`${bron.ism}: kelajakda`);
}Yechim
// Yadro
function bronHolati(bron, hozir) {
const bugun = hozir.toISOString().slice(0, 10);
if (bron.sana < bugun) return "o'tgan";
return bron.sana === bugun ? "bugun" : "kelajakda";
}
// Qobiq
const bronlar = [
{ ism: "Dilshod aka", sana: "2026-10-04" },
{ ism: "Malika", sana: "2026-10-05" },
{ ism: "Jasur", sana: "2026-10-12" },
];
const hozir = new Date("2026-10-05T12:00:00Z");
for (const bron of bronlar) {
console.log(`${bron.ism}: ${bronHolati(bron, hozir)}`);
}Konsolda:
Dilshod aka: o'tgan
Malika: bugun
Jasur: kelajakdabronHolati endi istalgan "hozir" bilan sinaladi — kelajakdagi yoki o'tgan sana bilan ham. Haqiqiy dasturda qobiq new Date() ni beradi. Diqqat: toISOString vaqtni UTC'da beradi. Toshkentda yarim tundan keyin soat 05:00 gacha UTC'da hali kecha — real loyihada mahalliy sanani olish kerak (Date va vaqt zonalari).
3-mashq (qiyin): Eng faol mehmonlar
Bronlar ro'yxatidan har mehmonning bronlar sonini hisoblang va eng ko'p bron qilgan ikkitasini "Ism: N ta" ko'rinishida qaytaring. Faqat deklarativ vositalar: Object.groupBy, Object.entries, map, toSorted, slice. Teng bo'lsa — ism bo'yicha alifbo tartibida (localeCompare).
const bronlar = [
{ ism: "Malika" }, { ism: "Jasur" }, { ism: "Malika" },
{ ism: "Dilshod aka" }, { ism: "Jasur" }, { ism: "Malika" },
];
console.log(engFaollar(bronlar)); // [ 'Malika: 3 ta', 'Jasur: 2 ta' ]Yechim
function engFaollar(bronlar, soni = 2) {
const guruhlar = Object.groupBy(bronlar, (bron) => bron.ism);
return Object.entries(guruhlar)
.map(([ism, royxat]) => ({ ism, soni: royxat.length }))
.toSorted((a, b) => b.soni - a.soni || a.ism.localeCompare(b.ism))
.slice(0, soni)
.map(({ ism, soni: n }) => `${ism}: ${n} ta`);
}
const bronlar = [
{ ism: "Malika" }, { ism: "Jasur" }, { ism: "Malika" },
{ ism: "Dilshod aka" }, { ism: "Jasur" }, { ism: "Malika" },
];
console.log(engFaollar(bronlar)); // [ 'Malika: 3 ta', 'Jasur: 2 ta' ]Taqqoslashdagi ||: sonlar farqi 0 bo'lsa (teng), ikkinchi mezon — ism ishlaydi. Oxirgi map da soni: n — destructuring'da nomini o'zgartirish, chunki soni nomi funksiya parametri bilan band. Butun funksiya — bitta ifoda, let ham, push ham yo'q.
4-mashq: Vazifalar qadami — statistika toza yadroda
Mashqlar repoda kurs/mashqlar/12/22-fp/statistika.mjs yarating. statistika(vazifalar) toza funksiyasi { jami, bajarilgan, faol, foiz } obyektini qaytarsin (foiz — bajarilganlarning butun foizi, ro'yxat bo'sh bo'lsa 0). Qobiq qismi — vazifalar ning boshlang'ich ma'lumotiga o'xshash massiv bilan uni chaqirib, natijani chiqarsin. Ishora: filter va Math.round.
Yechim
// Yadro
export function statistika(vazifalar) {
const jami = vazifalar.length;
const bajarilgan = vazifalar.filter((v) => v.bajarildi).length;
return {
jami,
bajarilgan,
faol: jami - bajarilgan,
foiz: jami === 0 ? 0 : Math.round((bajarilgan / jami) * 100),
};
}
// Qobiq
const vazifalar = [
{ id: 1, matn: "Non olish", bajarildi: false },
{ id: 2, matn: "DOM darsini takrorlash", bajarildi: true },
{ id: 3, matn: "Onamga qo'ng'iroq qilish", bajarildi: false },
];
console.log(statistika(vazifalar));
console.log(statistika([]));Konsolda:
{ jami: 3, bajarilgan: 1, faol: 2, foiz: 33 }
{ jami: 0, bajarilgan: 0, faol: 0, foiz: 0 }Bo'sh ro'yxat alohida tekshirildi — aks holda 0 / 0 NaN beradi. statistika faqat ma'lumot oladi va qaytaradi: uni keyinchalik vazifalar ilovasiga ham, testga ham o'zgarishsiz olib o'tish mumkin. Terminalda: node 12/22-fp/statistika.mjs.
git add 12/22-fp
git commit -m "12/22: vazifalar statistikasi toza funksiyada"10. Real ishda
- Kod review. "Bu
mapnimani o'zgartiryapti?", "Bu funksiyani testda qanday chaqiraman?" — FP g'oyalari review'dagi eng ko'p savollarga javob beradi. - React va holat boshqaruvi. Komponentlar — holatdan UI yasaydigan funksiyalar, reducer'lar — toza funksiyalar. FP tafakkurisiz React'da qiynalasiz.
- Ma'lumot qayta ishlash. Hisobotlar, analitika, server javoblarini formatlash — hammasi
filter/map/groupByzanjirlari bilan yoziladi. - Testlash. Funksional yadro — eng oson testlanadigan kod. Ko'p jamoalarda "biznes mantiq toza bo'lsin" degan qoida bor.
- Intervyu. "Imperativ va deklarativ farqi?", "Toza funksiya nima va nega muhim?", "Havola shaffofligi nima?" — junior va middle intervyularining doimiy savollari.
Xulosa
- Imperativ — "qanday" (sikl, hisoblagich, o'zgaruvchan holat); deklarativ — "nima" (
filter(tolanganmi).map(summasi)). - FP g'oyalari: birinchi darajali funksiya, toza funksiya, havola shaffofligi, ma'lumotni o'zgartirmaslik, ma'lumot oqimi, ko'rsatmadan ko'ra ifoda.
- Havola shaffof chaqiruvni o'z qiymati bilan almashtirish mumkin — shuning uchun uni alohida tushunish, sinash va keshlash oson.
- Funksional yadro, imperativ qobiq: hisob-kitob toza, yon ta'sirlar (vaqt, konsol, tarmoq) — chetda.
- Iterator helpers deklarativ zanjirga erta chiqishni qo'shadi; juda katta ma'lumot yoki murakkab mantiqda oddiy sikl ham to'g'ri tanlov.
Keyingi dars: Immutability chuqur — FP'ning uchinchi g'oyasi alohida: nega ma'lumotni o'zgartirmaslik kerak, Object.freeze va structuredClone qayerda yetmaydi va structural sharing uni qanday arzon qiladi.
Manbalar
- MDN: "Functional programming" (Glossary), "First-class Function", "Iterator.prototype.take()" — developer.mozilla.org
- Eric Elliott, "Composing Software" — FP asoslari JavaScript'da
- Gary Bernhardt, "Boundaries" (2012) — "Functional Core, Imperative Shell" — destroyallsoftware.com
- Kyle Simpson, "Functional-Light JavaScript" — github.com/getify/Functional-Light-JS
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!