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
- Ichki funksiya tashqi scope dagi o'zgaruvchini ishlatishi kerak
- 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 |