WisarWisar
Dasturlash kitobi/2-QISM — JavaScript14 daqiqa

2.4-bob: Execution context, hoisting, scope chain va closure (chuqur)

2-QISM — JavaScript (0 dan chuqurgacha) · 4-mavzu


1. Kirish va motivatsiya

Oldingi boblarda o'zgaruvchi 2.1-bob, shart/tsikl 2.2-bob va funksiya 2.3-bob — JS'ning **"nima"**sini ko'rdik. Bu bob esa "nega" va "qanday" ga javob beradi: JS kod aslida qanday ishga tushadi? Nega o'zgaruvchini e'londan oldin ishlatsa ba'zan undefined, ba'zan xato? Closure nima va nega u JS'ning eng kuchli (va eng ko'p so'raladigan) tushunchasi?

Bu bob — JS'ni havaskorona ishlatishdan chinakam tushunishga o'tish nuqtasi. Mana shu tushunchalar:

  • Execution context — kod ishlaydigan "muhit" (0.6: abstraction).
  • Hoisting — nega var va function "yuqoriga ko'tariladi".
  • TDZ — nega let/const e'londan oldin xato beradi.
  • Scope chain — JS o'zgaruvchini qanday "qidiradi" (2.3 scope ning ichki mexanizmi).
  • Closure — funksiyaning o'z "tug'ilgan muhitini eslab qolishi" — React hook'lar, modullar, event handler'larning asosi.

Diqqat: bu — intervyularning eng sevimli mavzusi. "Closure nima?" — frontend intervyularda deyarli doim so'raladi. Bu bobni o'zlashtirsangiz, JS sizga "sehr" emas, mexanizm bo'lib ko'rinadi.


2. Nazariya — chuqur tushuntirish

2.1. Execution context (bajarilish konteksti)

Execution context — JS kodi ishlaydigan muhit: u o'zgaruvchilar, funksiyalar, scope chain va this haqidagi ma'lumotni saqlaydi. Ikki asosiy tur:

  • Global Execution Context — dastur boshlanganda yaratiladi (butun fayl uchun bitta).
  • Function Execution Context — har funksiya chaqirilganda yangi yaratiladi.

Har bir context ikki bosqichda ishlaydi:

text
  1. CREATION (yaratish) bosqichi:
     - o'zgaruvchi va funksiyalar uchun XOTIRA ajratiladi (hoisting shu yerda!)
     - var  undefined bilan; let/const  "init qilinmagan" (TDZ); function  to'liq
     - scope chain va `this` o'rnatiladi

  2. EXECUTION (bajarish) bosqichi:
     - kod QATORMA-QATOR ishlaydi
     - o'zgaruvchilarga haqiqiy qiymat tayinlanadi

Mana shu ikki bosqich — hoisting va TDZ'ning sababi.

2.2. Call Stack — kontekstlar to'plami

Kontekstlar call stack'da (0.1: stack) saqlanadi — "oxirgi kirgan, birinchi chiqadi" (LIFO):

text
  function a() { b(); }
  function b() { c(); }
  function c() { console.log("salom"); }
  a();

  Call Stack o'zgarishi:
  ┌─────┐   ┌─────┐   ┌─────┐   ┌─────┐
  │     │   │  c  │   │     │   │     │
  │  b  │   │  b  │   │  b  │   │     │
  │  a  │  │  a  │  │  a  │  │  a  │  (bo'shaydi)
  │ glob│   │ glob│   │ glob│   │ glob│
  └─────┘   └─────┘   └─────┘   └─────┘
  a() chaq.  c gacha    c tugadi   hammasi tugadi

(0.1: Maximum call stack size exceeded — stack to'lib ketishi; cheksiz rekursiya.)

2.3. Hoisting (ko'tarilish)

Hoisting — JS'ning creation bosqichida 2.1-bob e'lonlarni scope "tepasiga ko'tarilgandek" ko'rsatishi. Lekin muhim: faqat e'lon ko'tariladi, tayinlash emas:

js
console.log(x);   // undefined (xato emas!) — var hoisted, undefined bilan
var x = 5;
console.log(x);   // 5

// Aslida JS buni shunday "ko'radi":
// var x;           e'lon yuqoriga (creation: undefined)
// console.log(x);  undefined
// x = 5;           tayinlash o'z joyida (execution)

Function declaration — to'liq hoisted (e'londan oldin chaqirsa bo'ladi):

js
salom();   //  "Salom!" — function declaration to'liq ko'tariladi
function salom() { return console.log("Salom!"); }

Lekin function expression va arrow — o'zgaruvchi qoidasiga bo'ysunadi:

js
salom();   //  xato — const hoisted, lekin init qilinmagan (TDZ — 2.4)
const salom = () => console.log("Salom!");

2.4. TDZ (Temporal Dead Zone) — let/const farqi

var hoisted bo'lib undefined oladi. let/const esa hoisted bo'ladi, lekin init qilinmaydi — ular e'lon qilingan qatorgacha **TDZ (vaqtinchalik o'lik zona)**da turadi va ularga murojaat ReferenceError beradi:

js
console.log(a);   // undefined  (var — hoisted + undefined)
console.log(b);   //  ReferenceError (let — TDZ)

var a = 1;
let b = 2;
text
  ┌──────────── TDZ (b uchun) ─────────────┐
  console.log(b);   bu yerda b'ga murojaat = ReferenceError
  let b = 2;        TDZ shu yerda tugaydi (b init bo'ldi)
  console.log(b);   endi xavfsiz (2)

Nega let/const afzal 2.1-bob: TDZ — bu himoya. U sizni o'zgaruvchini e'londan oldin xato ishlatishdan ogohlantiradi (var esa jimgina undefined berib, yashirin bug yaratadi). Shuning uchun varni ishlatmang.

2.5. Scope chain (scope zanjiri) — qidirish mexanizmi

Zanjirni tushunishdan oldin — scope (qamrov) nima va qanday turlari bor. Scope — o'zgaruvchi ko'rinadigan (murojaat qilsa bo'ladigan) hudud. JS'da uch tur:

  • Global scope — fayl (yoki modul) eng tashqarisi. Bu yerdagi o'zgaruvchi hamma joydan ko'rinadi.
  • Function scope (funksiya qamrovi) — funksiya ichi. Funksiya ichida e'lon qilingan o'zgaruvchi (shu jumladan var) faqat shu funksiya ichida yashaydi.
  • Block scope (blok qamrovi){ } ichi (if, for, oddiy blok). let/const block-scope: ular faqat e'lon qilingan blok ichida ko'rinadi. var esa block-scope'ni e'tiborsiz qoldiradi — u faqat function-scope 2.8-bob.
js
let global = "G";              // global scope

function f() {
  let funk = "F";              // function scope (faqat f ichida)
  if (true) {
    let blok = "B";            // block scope (faqat shu if ichida)
    var eskicha = "V";         // var — blokni "yorib o'tadi", f ichida qoladi
  }
  console.log(funk, eskicha);  //  "F V" — var blokdan chiqib ketdi
  // console.log(blok);        //  ReferenceError — blok scope tashqarida yo'q
}

Bu uch scope uyalangan (nested): block function ichida, function global ichida. Mana shu uyalanish keyingi qidiruv mexanizmini — scope chain'ni — keltirib chiqaradi.

Har execution context'ning lexical environmenti bor — o'zgaruvchi-qiymat jadvali + tashqi muhitga havola. Bu havolalar zanjiri — scope chain.

JS o'zgaruvchini qidirganda: ichkidan tashqiga qarab yuradi — topilguncha yoki global'gacha:

js
const global = "global";

function tashqi() {
  const tashqiVar = "tashqi";

  function ichki() {
    const ichkiVar = "ichki";
    console.log(ichkiVar);  // o'z scope'ida topadi
    console.log(tashqiVar); // yo'q  ota scope'da topadi (tashqi)
    console.log(global);    // yo'q  ota  ota (global)da topadi
  }
  ichki();
}
tashqi();
text
  Scope chain (qidirish yo'nalishi):
  ichki scope  tashqi scope  global scope
  (ichkiVar)    (tashqiVar)    (global)
       │             │             │
       └── topilmasa, tashqiga qarab yuradi ──┘

Muhim: qidiruv faqat ichkidan tashqiga (bola ota). Tashqi scope ichki scope o'zgaruvchisini ko'ra olmaydi 2.3-bob. Bu — leksik (lexical) scope: scope kod qayerda yozilgani bilan belgilanadi (qayerdan chaqirilgani bilan emas).

2.6. Closure (yopilma) — eng muhim tushuncha

Closure — funksiyaning o'zi + uning tug'ilgan leksik muhiti (scope chain) birga. Oddiyroq: ichki funksiya tashqi funksiyaning o'zgaruvchilarini "eslab qoladi" — hatto tashqi funksiya tugagandan keyin ham.

js
function hisoblagich() {
  let son = 0;              // bu o'zgaruvchi "yopib qolinadi"

  return function () {       // ichki funksiya son'ni "eslaydi"
    son++;
    return son;
  };
}

const oshir = hisoblagich();  // hisoblagich() tugadi, LEKIN son yashab qoladi!
console.log(oshir());   // 1
console.log(oshir());   // 2
console.log(oshir());   // 3   son saqlanib, oshib bormoqda (closure!)

Nima sodir bo'ldi? hisoblagich() tugadi, lekin u qaytargan ichki funksiya songa havolani ushlab turibdi. Shuning uchun son xotiradan o'chmaydi (0.1: garbage collection uni saqlaydi) va har chaqiriqda saqlanadi.

O'xshatish: closure — bu funksiyaning "orqa cho'ntagi". Funksiya tug'ilganda, atrofidagi o'zgaruvchilarni cho'ntagiga solib oladi va qayerga borsa ham o'zi bilan olib yuradi. Tashqi funksiya "uy" buzilsa ham, cho'ntakdagi narsa qoladi.

2.7. Closure nega muhim? (amaliy foydalar)

Closure — JS'ning butun ekotizimida ishlaydi:

  1. Private (xususiy) ma'lumotsonga faqat ichki funksiya orqali murojaat (tashqaridan ko'rinmaydi — inkapsulyatsiya):
js
function bank(boshlangich) {
  let balans = boshlangich;            // XUSUSIY — tashqaridan ko'rinmaydi
  return {
    qoy: (x) => balans += x,
    yech: (x) => balans -= x,
    korish: () => balans,
  };
}
const hisob = bank(100);
hisob.qoy(50);
console.log(hisob.korish());  // 150
// console.log(balans);  //  ko'rinmaydi — himoyalangan (closure!)
  1. Funksiya fabrikasi (2.3, Misol 4) — sozlangan funksiyalar yaratish.
  2. React hooks (useState) — closure ustiga qurilgan 11.5-bob!
  3. Event handler, callback, debounce/throttle — closure'ga tayanadi.

2.8. Mashhur closure tuzog'i — tsikl va var

Klassik intervyu savoli — var + tsikl + closure:

js
//  var bilan — hammasi 3 chiqaradi (!)
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// natija: 3, 3, 3   var function-scope, hammasi BITTA i'ni ulashadi

//  let bilan — 0, 1, 2
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// natija: 0, 1, 2   let block-scope, har iteratsiyada YANGI i

Sababi: var function-scope (bitta i hamma uchun); tsikl tugagach i=3, keyin callback'lar ishlaydi. let esa har iteratsiyada yangi binding yaratadi — closure har biri o'z isini eslaydi. Bu — let/const ning yana bir afzalligi 2.1-bob.


3. Asosiy tushunchalar — tez ma'lumotnoma

text
Execution Context  — kod ishlaydigan muhit (creation + execution bosqich)
Call Stack         — kontekstlar to'plami (LIFO, 0.1)
Hoisting           — e'lonlar "tepaga"; varundefined, functionto'liq
TDZ                — let/const e'londan oldin  ReferenceError
Scope chain        — o'zgaruvchi qidiruvi: ichkidan tashqiga
Lexical scope      — scope kod QAYERDA yozilganida belgilanadi
Closure            — funksiya + tug'ilgan muhiti (o'zgaruvchini "eslaydi")

4. Batafsil kod namunalari

Misol 1 — Hoisting farqlari (2.3, 2.4)

js
// var — hoisted, undefined
console.log(a);    // undefined
var a = 1;

// let — TDZ  xato
try { console.log(b); } catch (e) { console.log(e.name); }  // "ReferenceError"
let b = 2;

// function declaration — to'liq hoisted
salom();           //  ishlaydi
function salom() { console.log("Salom!"); }

// arrow/expression — TDZ
try { hayr(); } catch (e) { console.log(e.name); }  // "ReferenceError"
const hayr = () => console.log("Hayr!");

Misol 2 — Scope chain (2.5)

js
const daraja = "global";

function tashqi() {
  const daraja2 = "tashqi";
  function ichki() {
    const daraja3 = "ichki";
    // ichki  tashqi  global (qidiruv yo'nalishi)
    console.log(daraja3, daraja2, daraja);  // "ichki tashqi global"
  }
  ichki();
}
tashqi();

Misol 3 — Closure: hisoblagich va private holat (2.6, 2.7)

js
function yaratHisoblagich() {
  let son = 0;                    // private (closure ichida)
  return {
    oshir: () => ++son,
    kamaytir: () => --son,
    qiymat: () => son,
  };
}

const c = yaratHisoblagich();
c.oshir();
c.oshir();
c.kamaytir();
console.log(c.qiymat());          // 1
// son tashqaridan ko'rinmaydi — faqat metodlar orqali (inkapsulyatsiya)

Misol 4 — Closure tuzog'i va yechimi (2.8)

js
//  var — 3, 3, 3
console.log("var bilan:");
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);   // 3, 3, 3
}

//  let — 0, 1, 2
setTimeout(() => {
  console.log("let bilan:");
  for (let j = 0; j < 3; j++) {
    setTimeout(() => console.log(j), 0); // 0, 1, 2
  }
}, 50);

5. To'g'ri va noto'g'ri holatlar

1) var ishlatish (hoisting/scope muammosi)

js
//  var — hoisting bilan undefined, function-scope (2.3, 2.8)
function f() { if (true) { var x = 5; } console.log(x); } // 5 (blokdan "chiqib ketdi")

//  let/const — block-scope, TDZ himoyasi
function f() { if (true) { let x = 5; } /* x bu yerda yo'q */ }

2) O'zgaruvchini e'londan oldin ishlatish

js
//  TDZ  ReferenceError (2.4)
console.log(soni);
let soni = 5;

//  avval e'lon, keyin ishlat
let soni = 5;
console.log(soni);

3) Tsiklda var + closure

js
//  hammasi oxirgi qiymatni ko'radi (2.8)
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i));

//  let — har iteratsiya o'z scope'i
for (let i = 0; i < 3; i++) setTimeout(() => console.log(i));

4) Closure'ni tushunmay xotira oqishi (memory leak)

js
//  keraksiz katta ma'lumotni closure'da ushlab turish
function f() { const katta = yangiKattaMassiv(); return () => katta[0]; }
// katta butunlay xotirada qoladi (0.1: RAM)

//  faqat kerakini ushla
function f() { const birinchi = yangiKattaMassiv()[0]; return () => birinchi; }

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — ReferenceError: Cannot access 'x' before initialization

Sababi: let/constni TDZ'da (e'londan oldin) ishlatdingiz 2.4-bob. Yechimi: e'lonni ishlatishdan oldin qo'ying.

Xato 2 — var o'zgaruvchi kutilmagan qiymat (undefined)

Sababi: hoisting — e'lon ko'tarildi, tayinlash yo'q 2.3-bob. Yechimi: varni let/constga almashtiring.

Xato 3 — Tsiklda barcha callback bir xil qiymat

Sababi: var + closure 2.8-bob. Yechimi: let ishlating (har iteratsiyada yangi binding).

Xato 4 — Maximum call stack size exceeded

Sababi: cheksiz rekursiya — call stack to'ldi (2.2, 0.1). Yechimi: rekursiyaga to'xtash sharti (base case — 3.11-bob).

Xato 5 — Closure'da eski qiymat "qotib qolgan"

Sababi: closure o'zgaruvchiga havolani ushlaydi — kutilgan vaqtda o'zgargan/o'zgarmagan bo'lishi mumkin. Yechimi: mexanizmni tushuning 2.6-bob; React'da useEffect dependency 11.5-bob.


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • let/const 2.1-bob: TDZ va block-scope — nega var emas.
  • Funksiyalar 2.3-bob: scope, HOF — bu bobning poydevori.
  • Asinxron 2.11-bob: call stack + event loop 5.1-bob — closure callback'larda.
  • Modullar 2.14-bob: modul pattern — closure ustiga qurilgan.
  • React hooks 11.5-bob: useState/useEffect — closure mexanizmi; "stale closure" muammosi.
  • Funksional dasturlash 2.15-bob: closure, HOF.
  • Performance/memory (0.1, 11.11): closure va xotira oqishi.

8. Eng yaxshi amaliyotlar (best practices)

  • varni umuman ishlatmangconst/let (TDZ himoyasi, block-scope — 2.4, 2.8).
  • O'zgaruvchini ishlatishdan oldin e'lon qiling (TDZ'dan qoching).
  • Tsiklda doim let — closure tuzog'idan saqlaydi 2.8-bob.
  • Closure'ni private ma'lumot uchun ishlating (inkapsulyatsiya — 2.7).
  • Closure'da keraksiz katta ma'lumot ushlamang — xotira oqishi (5-bo'lim, 0.1).
  • Scope'ni iloji boricha kichik tuting — o'zgaruvchini kerakli blokda e'lon qiling.
  • Closure mexanizmini tushuning — React'da "stale closure" bug'larini oldini oladi 11.5-bob.
  • Leksik scope'ni eslang — funksiya qayerda yozilgani muhim, qayerdan chaqirilgani emas.

9. Amaliy loyiha: "Closure bilan Modullar va Holat Boshqaruvi"

Closure'ning amaliy kuchini ko'rsatuvchi loyiha — kutubxonasiz "holat boshqaruvi".

Maqsad

Execution context, scope chain va closure'ni amalda his qilib, private holat va modul pattern yaratish (React state'ning soddalashtirilgan g'oyasi).

Talablar (requirements)

  1. Hisoblagich moduli: yaratHisoblagich(boshlangich)oshir, kamaytir, qiymat, reset metodli obyekt qaytarsin; son private (closure — Misol 3).
  2. Bank moduli: bank(boshlangichBalans)qoy, yech, balans metodlari; manfiy balansga yo'l qo'ymang (yechda tekshiring); balans private 2.7-bob.
  3. Funksiya fabrikasi: kopaytuvchiYarat(n)nga ko'paytiruvchi funksiya qaytaradi (closure — 2.6). ikki, uch yarating.
  4. Hoisting tajribasi: var, let, function declaration va arrow'ni e'londan oldin ishlatib, har birining natijasini (undefined / ReferenceError / ishlaydi) izohlang (2.3, 2.4).
  5. Closure tuzog'i: var va let bilan tsikl + setTimeout farqini ko'rsating va nega ekanini izohlang 2.8-bob.
  6. ID generator: har chaqiriqda oshib boruvchi noyob ID qaytaruvchi closure (yaratIdGenerator).
  7. (Bonus) Oddiy "store": yaratStore(boshlangich)olish/ozgartirish/obuna (subscribe) bilan (Redux g'oyasi — 12.2).

Maslahatlar (hint)

  • Private o'zgaruvchi — tashqi funksiya ichida let; faqat qaytarilgan metodlar orqali murojaat 2.7-bob.
  • Bank yechda: if (summa > balans) return "Mablag' yetarli emas".
  • ID generator: let id = 0; return () => ++id;.
  • Hoisting tajribasida try/catch bilan xatoni ushlab, e.nameni chiqaring (Misol 1).
  • Closure tuzog'ida ikki tsikl yozing (var va let) — natijani solishtiring (Misol 4).

"Tayyor" mezonlari (acceptance criteria)

  • Hisoblagich va bank modullari ishlaydi; ichki holat tashqaridan ko'rinmaydi.
  • Bank manfiy balansga yo'l qo'ymaydi.
  • Funksiya fabrikasi (closure) to'g'ri ishlaydi.
  • Hoisting tajribasi 4 holatni (var/let/function/arrow) ko'rsatib, izohlangan.
  • Closure tuzog'i (var vs let) ko'rsatilgan va izohlangan.
  • ID generator noyob, oshib boruvchi ID beradi.
  • Kod izohlarida "nega" tushuntirilgan.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda JS'ning ichki mexanizmini chuqur ochdik:

  • Execution context — kod ishlaydigan muhit; creation (hoisting shu yerda) + execution bosqichlari. Call stack — kontekstlar to'plami (LIFO).
  • Hoisting: e'lonlar "tepaga"; var undefined, function declaration to'liq, let/const TDZ (ReferenceError).
  • Scope chain: o'zgaruvchi qidiruvi ichkidan tashqiga; leksik scope — kod qayerda yozilgani muhim.
  • Closure: funksiya + tug'ilgan muhiti; o'zgaruvchini eslaydi (tashqi funksiya tugagach ham). Private holat, funksiya fabrikasi, React hooks — hammasi shunga tayanadi.
  • Closure tuzog'i: tsiklda var (bitta binding) vs let (har iteratsiyada yangi).

Keyingi bob — 2.5-bob: this, prototype, prototypal inheritance (chuqur). Scope va closure'ni tushundik; endi JS'ning yana ikki "sirli" tushunchasini — this (kontekstga qarab o'zgaradi) va prototype (JS'ning meros mexanizmi) ni chuqur ochamiz. Bular OOP 2.10-bob va butun JS obyekt tizimining asosi.


Foydalanilgan rasmiy/ishonchli manbalar

  • MDN Web Docs — Closures, scope, lexical environment
  • MDN Web Docs — Hoisting, Temporal Dead Zone, let/const/var
  • MDN Web Docs — execution context, call stack

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
2.4-bob: Execution context, hoisting, scope chain va closure (chuqur) — Wisar