IlmHamroh
JavaScript Full-stack/10-qism. JavaScript chuqur23/65-dars20 daqiqa
Mundarija (33)

JavaScript closure: funksiya o'z muhitini eslab qoladi

Qisqacha: Closure — funksiya va u tug'ilgan muhitning birikmasi. Funksiya yaratilganda o'zi yozilgan joydagi o'zgaruvchilarga havolani "orqa cho'ntagiga" solib oladi. Shuning uchun tashqi funksiya ishini tugatgandan keyin ham ichki funksiya uning o'zgaruvchilarini o'qiy va o'zgartira oladi: hisoblagichYarat() qaytargan oshir() har chaqiruvda o'sha soni ni oshiraveradi.

Bu darsda

  • Closure nima ekanini va u qanday paydo bo'lishini o'z so'zingiz bilan tushuntira olasiz.
  • Funksiya tugagach uning o'zgaruvchilari qachon yo'qolmasligini bilasiz.
  • Closure qiymatning nusxasini emas, o'zgaruvchining o'zini eslashini kod bilan ko'rsata olasiz.
  • Chrome DevTools'da funksiyaning [[Scopes]] ini ochib, u nimani eslab qolganini ko'ra olasiz.
  • vazifalar dagi keyingiId qanday buzilishi mumkinligini ko'rib, uni yashiradigan id generatorini yozasiz.

Oldin bilishingiz kerak: Scope asoslari, Callback va HOF, Execution context va call stack, Leksik scope va scope zanjiri, TDZ.

1. Nega bu kerak?

08-qismda uchta sir ochilmay qolgan edi.

Birinchisi — Scope asoslari darsidagi buyurtma hisoblagichi. Har yangi buyurtmaga raqam berish uchun soni ni funksiyadan tashqarida e'lon qilgan edik:

js
let soni = 0;

function buyurtmaQabul(manzil) {
  soni++;
  console.log(`${soni}-buyurtma: ${manzil}`);
}

buyurtmaQabul("Chilonzor");
buyurtmaQabul("Yunusobod");

Konsolda:

text
1-buyurtma: Chilonzor
2-buyurtma: Yunusobod

Kod ishlaydi, lekin soni global. Faylning istalgan joyidagi istalgan kod soni = 0 deb yozib, hisobni buzib qo'yishi mumkin. O'sha darsda "hisoblagichni yashirin saqlashning chiroyli usuli bor" degan edik.

Ikkinchisi — Callback va HOF darsidagi chegirmaYaratuvchi(20). U ishini tugatdi, lekin qaytgan funksiya keyinroq foiz ni baribir bildi. Qanday qilib?

Uchinchisi — 09-qismdagi tugmalar. Sikl ichida har tugmaga tinglovchi qo'shdik, va har biri o'z ishini esladi (Holatdan UI chizish, addEventListener). "Bu xususiyatning nomi — closure, uni 10-qismda chuqur o'rganamiz" degan edik.

Uchala sirning javobi bitta. Bugun uni ochamiz.

Hayotdan o'xshatish: Toshkentdan boshqa shaharga o'qishga ketgan talaba. U uydan chiqib ketdi, lekin cho'ntagida uy kalitini olib yuradi. Uy joyida turibdi — chunki kimdir uning kalitini olib yuribdi. Talaba xohlagan payt qaytib, xonasidagi narsalarni ola oladi, joyini o'zgartira oladi. Closure ham shunday: ichki funksiya tashqariga "ketadi", lekin o'zi tug'ilgan uyning kalitini olib ketadi.

Closure — JavaScript'ning eng muhim va eng ko'p so'raladigan mavzularidan biri. U birinchi o'qishda g'alati tuyulishi tabiiy. Shuning uchun bu darsda sekin yuramiz: avval sirni qadamma-qadam ochamiz, keyin qoidani aytamiz, oxirida DevTools'da o'z ko'zimiz bilan ko'ramiz. Keyingi ikki darsda esa closure'ni amalda ishlatamiz va uning tuzoqlarini o'rganamiz.

2. Sirni yaqindan ko'ramiz

2.1 Hisoblagich yasovchi funksiya

soni ni globaldan olib, funksiya ichiga qo'yamiz. Bu funksiya hisoblagichni yasaydi va uni qaytaradi:

js
function hisoblagichYarat() {
  let soni = 0;
  return function oshir() {
    soni++;
    return soni;
  };
}

const oshir = hisoblagichYarat();

console.log(oshir()); // 1
console.log(oshir()); // 2
console.log(oshir()); // 3

Qatorma-qator:

  1. hisoblagichYarat() chaqirildi. Ichida let soni = 0 yaratildi.
  2. U ichki oshir funksiyasini qaytardi — chaqirmadi, faqat qaytardi (qavssiz).
  3. hisoblagichYarat ishini tugatdi. Biz qaytgan funksiyani oshir o'zgaruvchisiga saqladik.
  4. oshir() har chaqirilganda soni ni bittaga oshirdi: 1, 2, 3.

To'rtinchi qadamni yana o'qing. soni — hisoblagichYarat ning lokal o'zgaruvchisi. Funksiya allaqachon tugagan. Scope asoslari darsida "funksiya tugasa, uning o'zgaruvchilari yo'qoladi" degan edik. Unda soni qayerda yashayapti? Va nega u har safar 0 dan boshlanmay, o'sib boryapti?

2.2 Tashqaridan tegib bo'lmaydi

Yana bir narsani tekshiramiz — soni ga tashqaridan murojaat qilish:

js
function hisoblagichYarat() {
  let soni = 0;
  return function oshir() {
    soni++;
    return soni;
  };
}

const oshir = hisoblagichYarat();
oshir();
console.log(soni);
text
ReferenceError: soni is not defined

Tarjimasi: "soni aniqlanmagan". Global muhitda soni degan nom yo'q — u hisoblagichYarat ning ichida. Demak soni ni faqat oshir ko'radi. Uni nolga qaytarib yoki -999 qilib buzadigan boshqa kod yo'q. «Nega bu kerak?» bo'limidagi global soni muammosi hal bo'ldi.

Ikki fakt yonma-yon turibdi: soni tirik (oshir uni o'zgartiryapti), lekin hech kimga ko'rinmaydi. Buni tushuntirish uchun "muhit" degan tushunchaga qaytamiz.

Tekshirib ko'ring: hisoblagichYarat dagi return function oshir() {...} ni return oshir(); deb yozsak (ichki funksiya alohida e'lon qilingan bo'lsa), const oshir = hisoblagichYarat() ga nima tushadi?

Javob

Funksiya emas, son — 1. oshir() qavs bilan yozilgani uchun u darhol chaqirildi va natijasi qaytdi. Keyin oshir() deb chaqirsangiz — TypeError: oshir is not a function. Funksiyani qaytarishda qavs qo'yilmaydi — xuddi callback'ni uzatishdagidek (Callback va HOF).

3. Muhit va "orqa cho'ntak"

3.1 Eslab olamiz: har chaqiruv — yangi muhit

Execution context va Leksik scope darslarida ko'rganmiz: funksiya chaqirilganda JavaScript unga yangi muhit yaratadi. Leksik muhit (lexical environment) — o'zgaruvchilar nomi va qiymati yozilgan "jadval" hamda tashqi muhitga havola. Kod biror nomni ishlatsa, JavaScript uni avval joriy muhitdan qidiradi. Topilmasa — tashqi muhitdan, undan ham tashqarisidan, global muhitgacha. Bu zanjir — scope zanjiri.

Odatda funksiya tugashi bilan uning muhiti keraksiz bo'lib qoladi va JavaScript uni xotiradan tozalaydi. Lekin "odatda" — har doim degani emas.

3.2 Funksiya tug'ilgan joyini eslaydi

Bitta yangi qoida qo'shiladi:

Funksiya yaratilganda u o'zi yaratilgan muhitga havolani o'zida saqlab oladi.

Spetsifikatsiyada (JavaScript qoidalari yozilgan rasmiy hujjatda) bu yashirin xususiyat [[Environment]] deb ataladi. Ikki qavatli kvadrat qavs — "ichki, koddan ko'rinmaydi" degani: oshir.Environment deb yozib, uni o'qib bo'lmaydi. Biz uni orqa cho'ntak deymiz: har funksiya orqa cho'ntagida o'zi tug'ilgan uyning kalitini olib yuradi.

Endi hisoblagichYarat ni qaytadan o'qiymiz:

  • hisoblagichYarat() chaqirildi — yangi muhit yaratildi, unda soni: 0.
  • Shu muhit ichida oshir funksiyasi yaratildi. U orqa cho'ntagiga shu muhitning kalitini soldi.
  • hisoblagichYarat tugadi. Odatda uning muhiti tozalanardi. Lekin oshir hali tirik va u muhitga havola qilib turibdi. JavaScript ishlatilayotgan narsani o'chirmaydi — muhit saqlanib qoladi.
  • oshir() chaqirilganda, uning o'z yangi muhiti yaratiladi (u bo'sh — oshir da o'zgaruvchi yo'q). Bu yangi muhitning "tashqi muhiti" — orqa cho'ntakdagi muhit. soni shu yerdan topiladi.

Pastdagi Keyingi tugmasini bosib, har qadamda stek va muhitlarga qarang. Eng muhim lahza — 4-qadam: hisoblagichYarat stekdan chiqib ketadi, lekin soni yo'qolmaydi.

Diagrammada xuddi shu tuzilma, ikkinchi chaqiruvdan keyin. Uzluksiz strelka — har muhitning "tashqi muhiti" (qidiruv yo'li), punktir — funksiyaning orqa cho'ntagi:

flowchart LR
  C["oshir() chaqiruvi muhiti<br/>(o'z o'zgaruvchisi yo'q)"] -- "tashqi muhit" --> H
  O["oshir funksiyasi"] -. "orqa cho'ntak<br/>[[Environment]]" .-> H
  H["hisoblagichYarat muhiti<br/>soni: 2"] -- "tashqi muhit" --> G["global muhit<br/>hisoblagichYarat, oshir"]

Diagrammaga qarang: hisoblagichYarat ning chaqiruvi allaqachon yo'q, uning muhiti esa bor. Ikkalasi bir narsa emas. Chaqiruv — stekdagi vaqtinchalik yozuv, muhit — o'zgaruvchilar turadigan joy. Muhit kerak bo'lib turgan ekan, u yashaydi.

3.3 Closure'ning ta'rifi

Endi atamani aniq aytamiz.

Closure — funksiya va u yaratilgan leksik muhitning birikmasi. Inglizcha "close over" — "o'rab olmoq": funksiya o'z atrofidagi o'zgaruvchilarni "o'rab olib" ketadi. O'zbekcha tarjimasi yo'q — dasturchilar "closure" deb ishlatadi, biz ham shunday.

Ikki nozik joy bor:

  • Texnik jihatdan JavaScript'dagi har bir funksiya closure. Hatto global funksiya ham global muhitni orqa cho'ntagida olib yuradi. Lekin bu oddiy holatda hech narsa sezilmaydi.
  • Amalda "closure" deganda ichki funksiya tashqi funksiyadan uzoqroq yashagan holatni nazarda tutishadi: u qaytarildi, tinglovchi sifatida berildi yoki taymerga topshirildi. Aynan shunda "funksiya tugadi, lekin o'zgaruvchilari tirik" degan sehr ko'rinadi.

Endi 08-qismdagi chegirmaYaratuvchi sirining javobi tayyor:

js
function chegirmaYaratuvchi(foiz) {
  return (narx) => narx - (narx * foiz) / 100;
}

const yangiYil = chegirmaYaratuvchi(20);
console.log(yangiYil(100000)); // 80000

foiz — parametr, parametrlar ham muhitda turadi. Arrow funksiya yaratilganda orqa cho'ntagiga foiz: 20 turgan muhitni soldi. yangiYil(100000) chaqirilganda narx — o'z muhitida, foiz — orqa cho'ntakdagi muhitda topildi.

Uchinchi sir ham shu qoida bilan ochiladi. Holatdan UI chizish darsida for (const ish of holat.ishlar) sikli ichida () => ishniAlmashtir(ish.id) yozgan edik. Sikl har aylanishda yangi ish o'zgaruvchisini yaratadi: const — blokka tegishli, har aylanish esa — yangi blok. Har aylanishda yaratilgan arrow o'sha aylanishning muhitini cho'ntagiga soladi. Shuning uchun birinchi tugma birinchi ish ni, ikkinchisi ikkinchisini eslaydi.

Qisqasi, uchala sirning javobi:

  • Hisoblagich — soni ichki funksiyaning orqa cho'ntagida yashaydi.
  • chegirmaYaratuvchi — foiz parametri ham muhitda, u ham cho'ntakka tushadi.
  • Tugmalar — har aylanish — yangi muhit, har tinglovchi o'zinikini eslaydi.

Tekshirib ko'ring: Quyidagi kodda closure bormi? function salomBer() { const ism = "Ali"; console.log(ism); } salomBer();

Javob

Texnik jihatdan salomBer ham global muhitni eslaydi. Lekin amaldagi ma'nodagi closure yo'q: ichki funksiya yo'q, ism faqat chaqiruv davomida kerak va salomBer tugashi bilan uning muhiti tozalanadi. Closure "ko'rinadigan" bo'lishi uchun ichki funksiya tashqi funksiyadan uzoqroq yashashi kerak.

4. Har chaqiruv — yangi muhit

4.1 Ikki mustaqil hisoblagich

«Bahor» da ikki navbat bor: zalda o'tiradiganlar va olib ketadiganlar. Har biriga alohida raqam kerak. hisoblagichYarat ni ikki marta chaqiramiz:

js
function hisoblagichYarat() {
  let soni = 0;
  return function oshir() {
    soni++;
    return soni;
  };
}

const zal = hisoblagichYarat();
const olibKetish = hisoblagichYarat();

console.log(zal()); // 1
console.log(zal()); // 2
console.log(olibKetish()); // 1
console.log(zal()); // 3

olibKetish() 3 emas, 1 qaytardi. Sababi «Muhit va orqa cho'ntak» bo'limidagi qoidada: har chaqiruv yangi muhit yaratadi. hisoblagichYarat ikki marta chaqirildi — ikkita alohida muhit, ikkita alohida soni. zal birinchisining kalitini, olibKetish ikkinchisinikini olib yuradi. Ular bir-birining soni ini ko'rmaydi ham.

Xuddi ikki talabaning ikki xil uyi va ikki xil kaliti kabi. Kod bitta, muhitlar har chaqiruvda yangi.

4.2 Qaytgan funksiyani saqlamaslik

Shu qoidaning teskari tomoni ham bor. Bu kod nima chiqaradi?

js
function hisoblagichYarat() {
  let soni = 0;
  return function oshir() {
    soni++;
    return soni;
  };
}

console.log(hisoblagichYarat()()); // 1
console.log(hisoblagichYarat()()); // 1
console.log(hisoblagichYarat()()); // 1

hisoblagichYarat()() — "yasa va darhol chaqir". Har qatorda yangi hisoblagich yasaldi, bir marta chaqirildi va tashlab yuborildi. Uchta hisoblagich, har biri bir martadan ishladi. Hisob o'sishi uchun bitta hisoblagichni saqlab, uni qayta-qayta chaqirish kerak.

Tekshirib ko'ring: const a = hisoblagichYarat(); const b = a; dan keyin a(); a(); b(); — oxirgi chaqiruv nima qaytaradi?

Javob

3. b = a yangi hisoblagich yasamadi — bitta funksiyaga ikkinchi nom berdi (Havola semantikasi). a va b — bitta funksiya, bitta orqa cho'ntak, bitta soni. Yangi muhit faqat hisoblagichYarat() chaqirilganda paydo bo'ladi.

5. Closure o'zgaruvchini eslaydi, nusxasini emas

5.1 Jonli bog'lanish

Boshlovchilarning eng keng tarqalgan fikri: "closure yaratilgan paytdagi qiymatning nusxasini oladi". Bu noto'g'ri. Closure muhitning o'zini eslaydi — demak, o'zgaruvchi keyin o'zgarsa, closure yangi qiymatni ko'radi:

js
let oshNarxi = 35000;

function narxniAyt() {
  console.log(`Osh: ${oshNarxi} so'm`);
}

narxniAyt();
oshNarxi = 38000;
narxniAyt();

Konsolda:

text
Osh: 35000 so'm
Osh: 38000 so'm

narxniAyt yaratilganda oshNarxi 35 000 edi. Lekin u "35 000" degan raqamni emas, oshNarxi turgan joyni esladi. Ikkinchi chaqiruvda o'sha joyga qaradi — u yerda endi 38 000.

O'xshatishga qaytsak: talaba uyning rasmini emas, kalitini olib yurgan. Uyda mebel o'zgarsa, qaytib kelganda yangi mebelni ko'radi.

5.2 Ikki funksiya — bitta muhit

Bitta muhitda ikkita funksiya tug'ilsa, ikkalasi bir xil kalitni oladi. Biri o'zgartirgan narsani ikkinchisi ko'radi:

js
function kassaYarat() {
  let tushum = 0;
  function sot(narx) {
    tushum += narx;
  }
  function hisobot() {
    console.log(`Bugungi tushum: ${tushum} so'm`);
  }
  return [sot, hisobot];
}

const [sot, hisobot] = kassaYarat();
sot(35000);
sot(5000);
hisobot(); // Bugungi tushum: 40000 so'm

kassaYarat ikki funksiyani massivda qaytardi, biz ularni massiv destructuring bilan oldik. sot va hisobot bitta chaqiruvda, bitta muhitda tug'ilgan. Shuning uchun sot oshirgan tushum ni hisobot ko'radi. Tashqaridagi hech kim esa tushum ga tegolmaydi — faqat shu ikki funksiya orqali. Bu g'oyani keyingi darsda "private holat" deb ataymiz va ko'p ishlatamiz.

Tekshirib ko'ring: Quyidagi kod nima chiqaradi va nega? let ism = "Ali"; const salom = () => console.log(ism); ism = "Vali"; salom();

Javob

Vali. Arrow funksiya ism ning qiymatini emas, ism o'zgaruvchisining o'zini eslaydi. Chaqiruv paytida ism da "Vali" turibdi.

6. Qaysi muhit: yozilgan joy, chaqirilgan joy emas

Orqa cho'ntakka funksiya yaratilgan muhit tushadi. Funksiya keyin qayerga olib borilsa ham, qayerda chaqirilsa ham — cho'ntakdagi kalit o'zgarmaydi. Bu — Leksik scope darsidagi qoidaning closure'dagi davomi:

js
const filial = "Chilonzor";

function manzilniAyt() {
  console.log(`Filial: ${filial}`);
}

function boshqaJoydaChaqir() {
  const filial = "Yunusobod";
  manzilniAyt();
}

boshqaJoydaChaqir(); // Filial: Chilonzor

manzilniAyt boshqaJoydaChaqir ichida chaqirildi va o'sha yerda filial = "Yunusobod" bor. Lekin natija — "Chilonzor". manzilniAyt global muhitda tug'ilgan, orqa cho'ntagida global muhit. Chaqirilgan joydagi filial uni qiziqtirmaydi.

Shuning uchun closure oldindan aytib beriladigan: funksiya qaysi o'zgaruvchilarni ko'rishini bilish uchun uning qayerda chaqirilishini qidirish shart emas. Kodda u qayerda yozilganiga qarash kifoya.

7. DevTools'da closure'ni ko'rish

7.1 [[Scopes]]

Orqa cho'ntakni Chrome'da ko'rsa bo'ladi. Quyidagi kodni sahifangizga qo'ying (yoki Console panelida bajaring) va console.dir bilan funksiyani oching:

js
function hisoblagichYarat() {
  let soni = 0;
  const katta = new Array(1000).fill(1);
  return function oshir() {
    soni++;
    return soni;
  };
}

const oshir = hisoblagichYarat();
console.dir(oshir);

Chiqqan ƒ oshir() yonidagi uchburchakni bosing, eng pastda [[Scopes]] ni oching. Qisqartirilgan ko'rinishi:

text
ƒ oshir()
  name: "oshir"
  [[Scopes]]: Scopes[3]
    0: Closure (hisoblagichYarat) {soni: 0}
    1: Script {oshir: ƒ}
    2: Global {window: Window, self: Window, document: document, …}

Bu — scope zanjirining o'zi, ichkaridan tashqariga:

  • Closure (hisoblagichYarat) — orqa cho'ntak. Qavs ichida — qaysi funksiyaning muhiti. Ichida soni: 0. oshir() ni bir necha marta chaqirib, yana oching — son o'sganini ko'rasiz.
  • Script — oddiy <script> dagi const va let lar. Ular global, lekin window ga yozilmaydi (Global obyekt va globalThis darsida ko'ramiz).
  • Global — global obyekt (window) va unga yozilgan hamma narsa.

Brauzer konsoli funksiyani ƒ oshir() deb ko'rsatadi, Node esa [Function: oshir] deydi — ma'nosi bir xil. [[Scopes]] ni esa faqat brauzer DevTools'i ko'rsatadi.

7.2 Hamma narsa eslab qolinmaydi

Yuqoridagi Closure ichida katta yo'q. Qiziq: katta ham hisoblagichYarat ning muhitida edi-ku?

Chrome'da kodni V8 degan JavaScript dvigateli (kodni bajaradigan ichki dastur) bajaradi. U bu yerda aqlli ish qiladi: ichki funksiyalar ishlatmaydigan o'zgaruvchini orqa cho'ntakka solmaydi. Bizning oshir faqat soni ni ishlatadi — shuning uchun faqat u saqlandi, 1000 elementli massiv esa tozalandi. Bu — tezlik uchun optimizatsiya, til qoidasi emas. Uning bitta muhim istisnosini Closure tuzoqlari darsida ko'ramiz: u xotira sizib chiqishiga olib kelishi mumkin.

7.3 To'xtash nuqtasida

Closure'ni yana bir joyda ko'rasiz — Sources panelida. oshir ichidagi soni++; qatoriga to'xtash nuqtasi (breakpoint) qo'ying va oshir() ni chaqiring (DevTools bilan debug). O'ng tomondagi Scope bo'limida to'rt qavat chiqadi: Local (oshir'ning o'z muhiti — bo'sh), Closure (hisoblagichYarat) (soni shu yerda), Script va Global. Qiymatni shu yerda ikki marta bosib o'zgartirsa ham bo'ladi.

Tekshirib ko'ring: const zal = hisoblagichYarat(); zal(); zal(); dan keyin console.dir(zal) qilsangiz, Closure ichida nima bo'ladi? const olibKetish = hisoblagichYarat(); ning Closure ichida-chi?

Javob

zal da {soni: 2}, olibKetish da {soni: 0}. Ikki funksiya — ikki xil muhit. DevTools buni aniq ko'rsatadi: kod bir xil, [[Scopes]] dagi qiymatlar har xil.

8. Ko'p uchraydigan xatolar

8.1 Funksiya o'rniga natijani qaytarish

js
function hisoblagichYarat() {
  let soni = 0;
  function oshir() {
    soni++;
    return soni;
  }
  return oshir();
}

const oshir = hisoblagichYarat();
console.log(oshir());
text
TypeError: oshir is not a function

Tarjimasi: "oshir funksiya emas". return oshir() funksiyani chaqirib, natijasini — 1 sonini — qaytardi. oshir ga son tushdi, sonni esa chaqirib bo'lmaydi. Xato hisoblagichYarat da emas, keyingi qatorda chiqdi — shuning uchun avval "nima qaytardim?" deb qarang. Tuzatish: return oshir; — qavssiz.

8.2 Yasovchini har safar qayta chaqirish

«Har chaqiruv — yangi muhit» bo'limidagi hisoblagichYarat()() — xuddi shu xatoning qisqa shakli. Ko'pincha u funksiya ichida yashirinadi:

js
function hisoblagichYarat() {
  let soni = 0;
  return () => ++soni;
}

function buyurtmaQabul(manzil) {
  const navbat = hisoblagichYarat();
  console.log(`${navbat()}-buyurtma: ${manzil}`);
}

buyurtmaQabul("Chilonzor");
buyurtmaQabul("Yunusobod");

Konsolda:

text
1-buyurtma: Chilonzor
1-buyurtma: Yunusobod

Har buyurtmada hisoblagichYarat() qayta chaqirildi — har safar yangi soni = 0. Tuzatish: hisoblagichni bir marta, funksiyadan tashqarida yasang: const navbat = hisoblagichYarat();, buyurtmaQabul ichida esa faqat navbat() ni chaqiring.

8.3 Tashqi o'zgaruvchini soyalash

js
function hisoblagichYarat() {
  let soni = 0;
  return function oshir() {
    let soni = 0;
    soni++;
    return soni;
  };
}

const oshir = hisoblagichYarat();
console.log(oshir()); // 1
console.log(oshir()); // 1

Ichkarida yana let soni yozildi. Endi oshir o'z muhitida soni ni topadi va tashqisigacha bormaydi — soyalash (shadowing). Har chaqiruvda yangi 0. Tuzatish: ichki let ni olib tashlang — tashqi o'zgaruvchini yangilash uchun e'lon kerak emas.

8.4 "Closure nusxa oladi" deb o'ylash

«Jonli bog'lanish» bo'limidagi fikr. Agar closure yaratilgandan keyin o'zgaruvchi o'zgarsa, closure yangi qiymatni ko'radi. Bu ba'zan foydali, ba'zan esa kutilmagan natija beradi — ikkinchi holatni Closure tuzoqlari darsida "eskirgan closure" deb o'rganamiz.

9. Mashqlar

1-mashq (oson): Salomlashuvchi

salomlashuvchiYarat(salom) funksiyasini yozing. U ism qabul qiladigan funksiya qaytarsin. const ozbekcha = salomlashuvchiYarat("Assalomu alaykum"), keyin ozbekcha("Malika") → Assalomu alaykum, Malika!. Ikkinchi salomlashuvchini "Xush kelibsiz" bilan yasang.

Ishora: chegirmaYaratuvchi bilan bir xil qolip — parametr orqa cho'ntakda qoladi.

Yechim
js
function salomlashuvchiYarat(salom) {
  return (ism) => `${salom}, ${ism}!`;
}

const ozbekcha = salomlashuvchiYarat("Assalomu alaykum");
const mehmonga = salomlashuvchiYarat("Xush kelibsiz");

console.log(ozbekcha("Malika")); // Assalomu alaykum, Malika!
console.log(mehmonga("Bobur")); // Xush kelibsiz, Bobur!

Ikki chaqiruv — ikki muhit: birida salom "Assalomu alaykum", ikkinchisida "Xush kelibsiz". Har qaytgan arrow o'z muhitini eslaydi.

2-mashq (o'rta): Stol navbati

navbatYarat(harf) funksiyasini yozing. U har chaqirilganda keyingi navbat raqamini satr sifatida qaytaradigan funksiya bersin: "A-1", "A-2", ... Zal uchun "A", olib ketish uchun "B" navbatini yasab, aralash chaqiring.

Yechim
js
function navbatYarat(harf) {
  let raqam = 0;
  return () => {
    raqam++;
    return `${harf}-${raqam}`;
  };
}

const zal = navbatYarat("A");
const olibKetish = navbatYarat("B");

console.log(zal()); // A-1
console.log(zal()); // A-2
console.log(olibKetish()); // B-1
console.log(zal()); // A-3

Har muhitda ikki narsa: parametr harf va o'zgaruvchi raqam. harf faqat o'qiladi, raqam o'zgaradi — ikkalasi ham orqa cho'ntakda.

3-mashq (qiyin): Natijani oldindan ayting

Kodni ishga tushirmasdan, har console.log nima chiqarishini yozing. Keyin Node'da tekshiring.

js
let foiz = 10;

function chegirmaYarat() {
  let qoldiq = 0;
  return (narx) => {
    const chegirma = (narx * foiz) / 100;
    qoldiq += chegirma;
    return `${narx - chegirma} (jami: ${qoldiq})`;
  };
}

const kassa = chegirmaYarat();
console.log(kassa(30000));
foiz = 20;
console.log(kassa(30000));
const yangi = chegirmaYarat();
console.log(yangi(30000));
Yechim

Konsolda:

text
27000 (jami: 3000)
24000 (jami: 9000)
24000 (jami: 6000)
  1. Birinchi chaqiruvda foiz — 10, chegirma 3 000, qoldiq 3 000.
  2. foiz global, va closure uning o'zini eslaydi. 20 bo'lgach, kassa ham 20 ni ko'radi: chegirma 6 000, qoldiq 3 000 + 6 000 = 9 000.
  3. yangi — yangi chaqiruv, yangi qoldiq = 0. foiz esa hamon o'sha global — 20. Shuning uchun 6 000.

Bu mashqda scope zanjirining ikki qavati ham ishladi: qoldiq — chegirmaYarat muhitidan (har chaqiruvda yangi), foiz — global muhitdan (hammaga bitta).

4-mashq: Vazifalar qadami — keyingiId ni kim buzishi mumkin?

vazifalar da yangi vazifaning id si holat.keyingiId dan olinadi. holat esa oddiy obyekt — uni istalgan kod o'zgartira oladi. Bu xavfni o'z ko'zingiz bilan ko'ring, keyin closure bilan yechimni Node'da tayyorlang. Ilovaning o'ziga keyingi darsda ulaymiz.

1. Buzib ko'ring. Saytni Live Server'da oching, DevTools Console'da yozing:

js
holat.keyingiId = 1;

So'ng forma orqali "Choy damlash" vazifasini qo'shing va uning katagini belgilang. Nima bo'ldi?

2. Generatorni yozing. Node'da idYasovchiYarat(boshlangich) funksiyasini yozing. U har chaqirilganda keyingi id ni qaytaradigan funksiya bersin: avval boshlangich ning o'zi, keyin har safar bittaga ko'proq. Hisoblagichga tashqaridan tegib bo'lmasligini tekshiring.

Yechim

1. Yangi vazifa ham id: 1 oldi — endi ro'yxatda ikkita id: 1 bor. "Choy damlash" katagi bosilganda u belgilanmaydi, buning o'rniga "Non olish" belgilanadi! Tinglovchi id ni o'qib, vazifaniTop(1) ni chaqiradi, find esa birinchi topilganini qaytaradi. Xuddi shunday, "Choy damlash" ni o'chirsangiz — "Non olish" o'chadi. Bitta tasodifiy qator butun ilovani noto'g'ri ishlatdi.

Bu safar uni biz ataylab buzdik. Lekin katta ilovada holat ni o'nlab funksiya o'zgartiradi — ulardan bittasidagi xato yetarli.

2.

js
function idYasovchiYarat(boshlangich) {
  let keyingi = boshlangich;
  return () => keyingi++;
}

const yangiId = idYasovchiYarat(4);

console.log(yangiId()); // 4
console.log(yangiId()); // 5
console.log(yangiId()); // 6
console.log(typeof keyingi); // undefined

keyingi++ — avval joriy qiymatni qaytaradi, keyin oshiradi (Arifmetik operatorlar). Shuning uchun birinchi id — 4, ya'ni boshlangich ning o'zi. keyingi faqat qaytgan arrow'ning orqa cho'ntagida: tashqaridan uni na o'qib, na o'zgartirib bo'ladi. Ko'pi bilan yangiId() ni ortiqcha chaqirib, bitta raqamni "o'tkazib yuborish" mumkin — lekin takroriy id berish endi imkonsiz.

10. Real ishda

  • React hook'lari. React'dagi useState, useEffect va boshqa hook'lar closure ustiga qurilgan. Komponent ichidagi har tugma bosish funksiyasi o'sha chizishdagi holatni eslab qoladi. React'ni 17-qismda o'rganamiz — u yerda bu dars sizga ko'p yordam beradi.
  • Hodisa tinglovchilari. 09-qismda yozgan har bir addEventListener(..., () => {...}) — closure: arrow tashqi o'zgaruvchilarni (tugmani, holatni) eslaydi va oradan soatlar o'tib bosilganda ham ularni topadi.
  • Kutubxonalar. Lodash'dagi debounce, once, memoize kabi yordamchilar ichida closure turadi. Ularni keyingi darsda o'zimiz yozamiz.
  • Intervyu. "Closure nima?" — JavaScript intervyusining eng mashhur savoli. Yaxshi javob uch qismdan iborat: ta'rif (funksiya + leksik muhit), misol (hisoblagich) va foydasi (yashirin holat, fabrikalar). Bonus — "u qiymatni emas, o'zgaruvchini eslaydi" va DevTools'dagi [[Scopes]].

Xulosa

  • Funksiya yaratilganda o'zi yaratilgan muhitni "orqa cho'ntagiga" soladi ([[Environment]]).
  • Closure — funksiya va shu muhitning birikmasi. Muhit kerak bo'lib turgan ekan, tashqi funksiya tugasa ham u yashaydi.
  • Tashqi funksiyaning har chaqiruvi — yangi muhit: ikki hisoblagich bir-biriga xalaqit bermaydi.
  • Closure qiymatning nusxasini emas, o'zgaruvchining o'zini eslaydi. Bitta muhitda tug'ilgan funksiyalar uni bo'lishadi.
  • Qaysi muhit eslanishi funksiya yozilgan joyga bog'liq, chaqirilgan joyga emas.
  • Chrome DevTools: console.dir(fn) → [[Scopes]] → Closure (...); Sources'da — Scope bo'limi.

Keyingi dars: Closure amaliyotda — closure bilan private holat, fabrika funksiyalar, once, oddiy kesh va debounce ning o'zini yozamiz, vazifalar ga esa id generatorini ulaymiz.

Manbalar

  • MDN: "Closures" — developer.mozilla.org
  • ECMAScript 2025 Language Specification: "Environment Records", "[[Environment]]" (OrdinaryFunctionCreate) — tc39.es/ecma262
  • Chrome for Developers: "JavaScript debugging reference" — Scope pane — developer.chrome.com/docs/devtools
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript closure: funksiya o'z muhitini eslab qoladi — IlmHamroh