Series: JavaScript Basics Lesson 16

Closure

Closure nima, V8 ichida qanday ishlaydi, [[Environment]] pointer, va closure ning klassik holatlar — counter va loop tuzo'g'i.

16-dars

Closure

Avval — Closure nima emas

Ko'pchilik Closure ni "funksiya ichida funksiya" deb tushunadi. Bu noto'g'ri.

function outer() {
  function inner() {
    console.log('men inner man');
  }
  inner();
}

Bu yerda closure yo'q — inner tashqi scope dagi hech narsani ishlatmayapti.


Closure uchun ikkita shart

  1. Ichki funksiya tashqi scope dagi o'zgaruvchini ishlatishi kerak
  2. Bu ichki funksiya tashqi funksiya tugagandan keyin ham chaqirilishi imkoni bo'lishi kerak

Closure — aniq ta'rif

function outer() {
  const x = 10; // tashqi o'zgaruvchi

  return function inner() {
    console.log(x); // tashqi o'zgaruvchini ishlatdi
  };
}

const fn = outer();
fn(); // 10 — outer tugagan bo'lsa ham!

outer tugadi. Lekin x o'chib ketmadi. Nega?

Chunki inner uni ushlab qoldi — bu Closure.


V8 ichida Closure qanday ishlaydi

Oddiy funksiyada o'zgaruvchi qayerda yashaydi?

function foo() {
  const x = 10; // stack da yashaydi
  return x;
}

x — stack da. foo tugagach stack frame o'chiriladi, x yo'qoladi. Tez, arzon.

Closure da o'zgaruvchi qayerda yashaydi?

function outer() {
  const x = 10; // endi heap da yashaydi!

  return function inner() {
    console.log(x);
  };
}

V8 parse vaqtida inner funksiyasini ko'rib, uning x ni ishlatayotganini aniqlaydi. Shu zahoti qaror qiladi: x ni stackda emas, heapda saqlash kerak — chunki outer tugagandan keyin ham x tirik qolishi kerak.

Bu jarayon — "variable lifting to heap" — developer ko'rmaydi, V8 avtomatik qiladi.

Stack:  [ outer frame ] → tugagach o'chiriladi
Heap:   Context { x: 10 } → inner ko'rsatib turgancha SAQLANADI

[[Environment]] — Closure ning texnik asosi

Har bir funksiya yaratilgan zahoti V8 unga yashirin ichki slot beradi:

innerFunction {
  [[Call]]: <kod>
  [[Environment]]: → outer ning Lexical Environment { x: 10 }
}

[[Environment]] — bu pointer. U "men shu joyda tug'ilganman" deydi. Funksiya chaqirilganda kerakli o'zgaruvchi o'zida topilmasa, shu pointer orqali tashqariga chiqib o'qiydi.

Bu pointer — closure ning haqiqiy mexanizmi.

15-dars da o'rgangan Scope Chain ham xuddi shu [[Environment]] pointer orqali quriladi.


Closure — ikkita klassik holat

Holat 1 — Klassik: return orqali

function makeCounter() {
  let count = 0;

  return {
    increment() { count++; },
    decrement() { count--; },
    getCount()  { return count; }
  };
}

const counter = makeCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
counter.getCount(); // 2

Bu yerda uchta funksiya bir xil count ga closure qilgan. Ularning barchasi bitta Lexical Environment dagi bitta count ni ko'rsatib turibdi:

Heap:
Context { count: 0 }  ← uchala metod shu yerga ishora qiladi
     ↑
counter.increment [[Environment]] ──┐
counter.decrement [[Environment]] ──┤→ bir xil Context
counter.getCount  [[Environment]] ──┘

Shuning uchun increment chaqirilsa, getCount ham yangi qiymatni ko'radi — ular bir xil count ni o'qiydi.


Holat 2 — Loop ichida Closure (klassik tuzog'i)

for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 3, 3, 3 — kutilgan: 0, 1, 2
  }, 100);
}

Nima bo'ldi?

var — function scope. Loop ichida uchta alohida i yaratilmadi — bitta i bor, global scope da. Uchta callback ham shu bitta i ga closure qildi.

Heap:
GlobalContext { i: 3 }  ← loop tugagach i = 3
     ↑
callback1 [[Environment]] ──┐
callback2 [[Environment]] ──┤→ hammasi bitta i ga
callback3 [[Environment]] ──┘

100ms o'tgach, hammasi i ni o'qiydi — u allaqachon 3.

Yechim — let bilan

for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 0, 1, 2
  }, 100);
}

let — block scope. Har iteratsiyada yangi i yaratiladi. Uchta callback uchta alohida i ga closure qiladi:

Heap:
Context1 { i: 0 } ← callback1 [[Environment]]
Context2 { i: 1 } ← callback2 [[Environment]]
Context3 { i: 2 } ← callback3 [[Environment]]
var let
Scope function block
Loop da bitta i har iteratsiyada yangi i
setTimeout natija 3, 3, 3 0, 1, 2