25 август 2025

O'zgaruvchi doirasi, yopilish (closure)

JavaScript juda funktsiya-yo’naltirilgan tildir. U bizga katta erkinlik beradi. Funktsiya istalgan vaqtda yaratilishi, boshqa funktsiyaga argument sifatida uzatilishi va keyinroq kodning butunlay boshqa joyidan chaqirilishi mumkin.

Biz allaqachon funktsiya o’zidan tashqaridagi o’zgaruvchilarga (“tashqi” o’zgaruvchilar) kira olishini bilamiz.

Lekin agar funktsiya yaratilgandan so’ng tashqi o’zgaruvchilar o’zgarsa nima bo’ladi? Funktsiya yangi qiymatlarni oladimi yoki eskilarini?

Va agar funktsiya argument sifatida uzatilib, kodning boshqa joyidan chaqirilsa, u yangi joydagi tashqi o’zgaruvchilarga kirish huquqiga ega bo’ladimi?

Ushbu stsenariylar va yanada murakkab holatlarni tushunish uchun bilimimizni kengaytiraylik.

Bu yerda let/const o’zgaruvchilar haqida gaplashamiz

JavaScript da o’zgaruvchi e’lon qilishning 3 xil usuli bor: let, const (zamonaviy usullar) va var (o’tmishdan qolgan).

  • Ushbu maqolada biz misollarda let o’zgaruvchilarini ishlatamiz.
  • const bilan e’lon qilingan o’zgaruvchilar ham xuddi shunday harakat qiladi, shuning uchun bu maqola const haqida ham.
  • Eski var ning bir nechta muhim farqlari bor, ular Eski "var" maqolasida yoritiladi.

Kod bloklari

Agar o’zgaruvchi kod bloki {...} ichida e’lon qilingan bo’lsa, u faqat shu blok ichida ko’rinadi.

Misol uchun:

{
  // tashqarida ko'rinmasligi kerak bo'lgan mahalliy o'zgaruvchilar bilan biror ish qiling

  let message = "Salom"; // faqat shu blokda ko'rinadi

  alert(message); // Salom
}

alert(message); // Xato: message aniqlanmagan

Buni faqat o’ziga tegishli o’zgaruvchilar bilan o’z vazifasini bajaradigan kod qismini ajratish uchun ishlatishimiz mumkin:

{
  // xabarni ko'rsatish
  let message = "Salom";
  alert(message);
}

{
  // boshqa xabarni ko'rsatish
  let message = "Xayr";
  alert(message);
}
Bloklarsiz xatolik bo’ladi

E’tibor bering, alohida bloklar bo’lmasa, mavjud o’zgaruvchi nomi bilan let dan foydalansak xatolik bo’ladi:

// xabarni ko'rsatish
let message = "Salom";
alert(message);

// boshqa xabarni ko'rsatish
let message = "Xayr"; // Xato: o'zgaruvchi allaqachon e'lon qilingan
alert(message);

if, for, while va boshqalar uchun ham {...} da e’lon qilingan o’zgaruvchilar faqat ichkarida ko’rinadi:

if (true) {
  let phrase = "Salom!";

  alert(phrase); // Salom!
}

alert(phrase); // Xato, bunday o'zgaruvchi yo'q!

Bu yerda if tugagandan so’ng, pastdagi alert phrase ni ko’rmaydi, shuning uchun xatolik.

Bu juda yaxshi, chunki bizga if shoxiga xos blok-mahalliy o’zgaruvchilar yaratishga imkon beradi.

Xuddi shunday narsa for va while sikllariga ham tegishli:

for (let i = 0; i < 3; i++) {
  // i o'zgaruvchisi faqat shu for ichida ko'rinadi
  alert(i); // 0, keyin 1, keyin 2
}

alert(i); // Xato, bunday o'zgaruvchi yo'q

Ko’rinishda let i {...} dan tashqarida. Lekin for konstruktsiyasi bu yerda maxsus: unda e’lon qilingan o’zgaruvchi blokning bir qismi hisoblanadi.

Ichma-ich funktsiyalar

Funktsiya boshqa funktsiya ichida yaratilganda “ichma-ich” deb ataladi.

JavaScript da buni qilish oson.

Biz buni kodimizni tartibga solish uchun ishlatishimiz mumkin:

function sayHiBye(firstName, lastName) {

  // pastda ishlatish uchun yordamchi ichma-ich funktsiya
  function getFullName() {
    return firstName + " " + lastName;
  }

  alert( "Salom, " + getFullName() );
  alert( "Xayr, " + getFullName() );

}

Bu yerda ichma-ich funktsiya getFullName() qulaylik uchun yaratilgan. U tashqi o’zgaruvchilarga kira oladi va shuning uchun to’liq ismni qaytara oladi. Ichma-ich funktsiyalar JavaScript da juda keng tarqalgan.

Yanada qiziqarli tomoni shundaki, ichma-ich funktsiya qaytarilishi mumkin: yangi objektning xususiyati sifatida yoki o’zi natija sifatida. Keyin uni boshqa joyda ishlatish mumkin. Qayerda bo’lishidan qat’i nazar, u hali ham bir xil tashqi o’zgaruvchilarga kirish huquqiga ega.

Quyida makeCounter har bir chaqiruvda keyingi raqamni qaytaradigan “hisoblagich” funktsiyasini yaratadi:

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2

Oddiy bo’lishiga qaramay, shu kodning bir oz o’zgartirilgan variantlari amaliy foydalanishga ega, masalan, avtomatlashtirilgan testlar uchun tasodifiy qiymatlar yaratish uchun tasodifiy raqam generatori sifatida.

Bu qanday ishlaydi? Agar biz bir nechta hisoblagich yaratsak, ular mustaqil bo’ladimi? Bu yerda o’zgaruvchilar bilan nima sodir bo’lyapti?

Bunday narsalarni tushunish JavaScript ning umumiy bilimi uchun ajoyib va murakkab stsenariylar uchun foydali. Shuning uchun biroz chuqurroq kiraylik.

Leksik muhit (Lexical Environment)

Bu yerda ajdaholar bor!

Chuqur texnik tushuntirish oldinda.

Men past darajadagi til tafsilotlaridan qochishni xohlasam ham, ularsiz har qanday tushunish etishmaydi va to’liq bo’lmaydi, shuning uchun tayyor bo’ling.

Aniqlik uchun tushuntirish bir necha bosqichga bo’lingan.

1-qadam. O’zgaruvchilar

JavaScript da har bir ishlaydigan funktsiya, kod bloki {...} va umuman skriptning Leksik muhit deb ataladigan ichki (yashirin) bog’langan objekti bor.

Leksik muhit objekti ikki qismdan iborat:

  1. Environment Record – barcha mahalliy o’zgaruvchilarni o’z xususiyatlari sifatida saqlaydigan objekt (va this qiymati kabi boshqa ma’lumotlar).
  2. Tashqi leksik muhitga havola, tashqi kod bilan bog’langan.

“O’zgaruvchi” – bu maxsus ichki objekt Environment Record ning xususiyati. “O’zgaruvchini olish yoki o’zgartirish” degani “shu objektning xususiyatini olish yoki o’zgartirish”.

Ushbu oddiy kodda funktsiyalarsiz faqat bitta Leksik muhit bor:

Bu butun skript bilan bog’langan global Leksik muhit deb ataladi.

Yuqoridagi rasmda to’rtburchak Environment Record (o’zgaruvchi ombori) ni, o’q esa tashqi havolani anglatadi. Global Leksik muhitning tashqi havolasi yo’q, shuning uchun o’q null ga yo’naltirilgan.

Kod bajarilishni boshlashi va davom etishi bilan Leksik muhit o’zgaradi.

Mana biroz uzunroq kod:

O’ng tarafdagi to’rtburchaklar ijro davomida global Leksik muhit qanday o’zgarishini ko’rsatadi:

  1. Skript boshlanganida, Leksik muhit barcha e’lon qilingan o’zgaruvchilar bilan oldindan to’ldiriladi.
    • Dastlab ular “Ishga tushirilmagan” holatda. Bu maxsus ichki holat, ya’ni mexanizm o’zgaruvchi haqida biladi, lekin u let bilan e’lon qilinmaguncha unga murojaat qilib bo’lmaydi. Bu deyarli o’zgaruvchi mavjud emasdek.
  2. Keyin let phrase ta’rifi paydo bo’ladi. Hali tayinlash yo’q, shuning uchun uning qiymati undefined. Shu nuqtadan boshlab o’zgaruvchini ishlatishimiz mumkin.
  3. phrase ga qiymat tayinlanadi.
  4. phrase qiymatni o’zgartiradi.

Hozircha hamma narsa oddiy ko’rinadi, to’g’rimi?

  • O’zgaruvchi – bu hozirda ijro etilayotgan blok/funktsiya/skript bilan bog’langan maxsus ichki objektning xususiyati.
  • O’zgaruvchilar bilan ishlash aslida shu objektning xususiyatlari bilan ishlash.
Leksik muhit spetsifikatsiya objekti

“Leksik muhit” spetsifikatsiya objekti: u faqat narsalar qanday ishlashini tasvirlash uchun til spetsifikatsiyasida “nazariy jihatdan” mavjud. Biz bu objektni kodimizda ololmaymiz va to’g’ridan-to’g’ri boshqara olmaymiz.

JavaScript mexanizmlari ham uni optimallashtirishi, xotirani tejash uchun ishlatilmaydigan o’zgaruvchilarni tashlab yuborishi va boshqa ichki hiyla-nayranglarni bajarishi mumkin, faqat ko’rinadigan xatti-harakatlar tasvirlangandek qolishi shartida.

2-qadam. Funktsiya e’lonlari

Funktsiya ham o’zgaruvchi kabi qiymatdir.

Farqi shundaki, Funktsiya e’loni darhol to’liq ishga tushiriladi.

Leksik muhit yaratilganda, Funktsiya e’loni darhol ishlatishga tayyor funktsiyaga aylanadi (let dan farqli o’laroq, u e’longacha foydalanib bo’lmaydi).

Shuning uchun biz Funktsiya e’loni sifatida e’lon qilingan funktsiyani hatto e’londan oldin ham ishlatishimiz mumkin.

Masalan, mana funktsiya qo’shganda global Leksik muhitning dastlabki holati:

Tabiiyki, bu xatti-harakat faqat Funktsiya e’lonlariga tegishli, let say = function(name)... kabi o’zgaruvchiga funktsiya tayinlaydigan Funktsiya ifodalariga emas.

3-qadam. Ichki va tashqi Leksik muhit

Funktsiya ishlaganda, chaqiruv boshida chaqiruvning mahalliy o’zgaruvchilari va parametrlarini saqlash uchun avtomatik ravishda yangi Leksik muhit yaratiladi.

Masalan, say("John") uchun u shunday ko’rinadi (ijro o’q bilan belgilangan qatorda):

Funktsiya chaqiruvi davomida bizda ikkita Leksik muhit bor: ichki (funktsiya chaqiruvi uchun) va tashqi (global):

  • Ichki Leksik muhit say ning joriy ijrosiga mos keladi. Unda bitta xususiyat bor: name, funktsiya argumenti. Biz say("John") ni chaqirdik, shuning uchun name ning qiymati "John".
  • Tashqi Leksik muhit global Leksik muhitdir. Unda phrase o’zgaruvchisi va funktsiyaning o’zi bor.

Ichki Leksik muhitda tashqi ga havola bor.

Kod o’zgaruvchiga kirmoqchi bo’lganda – avval ichki Leksik muhit qidiriladi, keyin tashqi, keyin undan ham tashqi va hokazo global gacha.

Agar o’zgaruvchi hech qayerda topilmasa, bu qat’iy rejimda xato (use strict siz, mavjud bo’lmagan o’zgaruvchiga tayinlash eski kod bilan moslashish uchun yangi global o’zgaruvchi yaratadi).

Ushbu misolda qidiruv quyidagicha davom etadi:

  • name o’zgaruvchisi uchun say ichidagi alert uni darhol ichki Leksik muhitda topadi.
  • phrase ga kirmoqchi bo’lganda, mahalliy phrase yo’q, shuning uchun u tashqi Leksik muhitga havolani kuzatib boradi va uni u yerda topadi.

4-qadam. Funktsiyani qaytarish

makeCounter misoliga qaytalik.

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();

Har bir makeCounter() chaqiruvi boshida, shu makeCounter ishi uchun o’zgaruvchilarni saqlash uchun yangi Leksik muhit objekti yaratiladi.

Shunday qilib bizda yuqoridagi misoldagidek ikkita ichma-ich Leksik muhit bor:

Farq shundaki, makeCounter() ijrosi davomida faqat bir qatorli kichik ichma-ich funktsiya yaratiladi: return count++. Biz uni hali ishga tushirmaymiz, faqat yaratamiz.

Barcha funktsiyalar ular yaratilgan Leksik muhitni eslab qoladi. Texnik jihatdan, bu yerda sehr yo’q: barcha funktsiyalar [[Environment]] nomli yashirin xususiyatga ega bo’lib, u funktsiya yaratilgan Leksik muhitga havolani saqlaydi:

Shunday qilib, counter.[[Environment]] {count: 0} Leksik muhitga havolaga ega. Funktsiya qayerda chaqirilishidan qat’i nazar, u qayerda yaratilganini shunday eslab qoladi. [[Environment]] havolasi funktsiya yaratilish vaqtida bir marta va abadiy o’rnatiladi.

Keyinroq counter() chaqirilganda, chaqiruv uchun yangi Leksik muhit yaratiladi va uning tashqi Leksik muhit havolasi counter.[[Environment]] dan olinadi:

Endi counter() ichidagi kod count o’zgaruvchisini qidirganda, u avval o’zining Leksik muhitini qidiradi (bo’sh, chunki u yerda mahalliy o’zgaruvchilar yo’q), keyin tashqi makeCounter() chaqiruvining Leksik muhitini, u yerda uni topadi va o’zgartiradi.

O’zgaruvchi yashaydigan Leksik muhitda yangilanadi.

Mana ijrodan keyingi holat:

Agar counter() ni bir necha marta chaqirsak, count o’zgaruvchisi bir xil joyda 2, 3 va hokazoga oshiriladi.

Yopilish (Closure)

“Yopilish” umumiy dasturlash atamasi bor, dasturchilar uni odatda bilishi kerak.

Yopilish – bu o’zining tashqi o’zgaruvchilarini eslaydigan va ularga kira oladigan funktsiya. Ba’zi tillarda bu mumkin emas yoki funktsiyani buning amalga oshishi uchun maxsus usulda yozish kerak. Lekin yuqorida tushuntirilganidek, JavaScript da barcha funktsiyalar tabiiy ravishda yopilishdir (bitta istisno bor, u "new Function" sintaksisi da yoritiladi).

Ya’ni: ular yashirin [[Environment]] xususiyatidan foydalanib qayerda yaratilganini avtomatik ravishda eslaydi va keyin ularning kodi tashqi o’zgaruvchilarga kira oladi.

Suhbatda frontend dasturchi “yopilish nima?” degan savol olganda, to’g’ri javob yopilishning ta’rifi va JavaScript dagi barcha funktsiyalar yopilish ekanligini tushuntirish va ehtimol texnik tafsilotlar haqida yana bir necha so’z bo’ladi: [[Environment]] xususiyati va Leksik muhitlar qanday ishlashi.

Axlat yig’ish (Garbage collection)

Odatda, Leksik muhit funktsiya chaqiruvi tugagandan so’ng barcha o’zgaruvchilar bilan birga xotiradan olib tashlanadi. Chunki unga hech qanday havola yo’q. Har qanday JavaScript objekti kabi, u faqat yetib boriladigan bo’lgancha xotirada saqlanadi.

Biroq, agar funktsiya tugagandan keyin ham yetib boriladigan ichma-ich funktsiya bo’lsa, u holda uning leksik muhitga havola qiladigan [[Environment]] xususiyati bor.

Bunday holda Leksik muhit funktsiya tugagandan keyin ham yetib borish mumkin, shuning uchun u tirik qoladi.

Misol uchun:

function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f(); // g.[[Environment]] tegishli f() chaqiruvining
// Leksik muhitiga havolani saqlaydi

E’tibor bering, agar f() ko’p marta chaqirilsa va natijada olingan funktsiyalar saqlansa, u holda barcha tegishli Leksik muhit objektlari ham xotirada saqlanadi. Quyidagi kodda ularning 3 tasi:

function f() {
  let value = Math.random();

  return function() { alert(value); };
}

// massivdagi 3 ta funktsiya, ularning har biri tegishli f() ishidan
// Leksik muhitga bog'lanadi
let arr = [f(), f(), f()];

Leksik muhit objekti yetib borilmaydigan bo’lganda o’ladi (boshqa objektlar kabi). Boshqacha qilib aytganda, u faqat unga havola qiladigan kamida bitta ichma-ich funktsiya mavjud bo’lgancha mavjud.

Quyidagi kodda ichma-ich funktsiya olib tashlangandan keyin, uning o’rab turgan Leksik muhiti (va shuning uchun value) xotiradan tozalanadi:

function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f(); // g funktsiyasi mavjud ekan, qiymat xotirada qoladi

"g = null; // ...va endi xotira tozalanadi

Haqiqiy hayotdagi optimallashtirish

Ko’rganimizdek, nazariy jihatdan funktsiya tirik ekan, barcha tashqi o’zgaruvchilar ham saqlanadi.

Lekin amalda JavaScript mexanizmlari buni optimallashtirmeye harakat qiladi. Ular o’zgaruvchi ishlatilishini tahlil qiladi va koddan tashqi o’zgaruvchi ishlatilmasligni aniq bo’lsa – u olib tashlanadi.

V8 (Chrome, Edge, Opera) da muhim yon ta’sir shundaki, bunday o’zgaruvchi debugging da mavjud bo’lmaydi.

Quyidagi misolni Chrome da Developer Tools ochiq holda ishlatib ko’ring.

U to’xtaganda, konsolda alert(value) ni yozing.

function f() {
  let value = Math.random();

  function g() {
    debugger; // konsolda: alert(value) yozing; Bunday o'zgaruvchi yo'q!
  }

  return g;
}

let g = f();
g();

Ko’rganingizdek – bunday o’zgaruvchi yo’q! Nazariy jihatdan, u mavjud bo’lishi kerak edi, lekin mexanizm uni optimallashtirdi.

Bu kulgili (agar unchalik vaqt talab qilmasa) debugging muammolariga olib kelishi mumkin. Ulardan biri – biz kutilgan o’rniga bir xil nomli tashqi o’zgaruvchini ko’rishimiz mumkin:

let value = "Ajablanarli!";

function f() {
  let value = "eng yaqin qiymat";

  function g() {
    debugger; // konsolda: alert(value) yozing; Ajablanarli!
  }

  return g;
}

let g = f();
g();

V8 ning bu xususiyatini bilish yaxshi. Agar siz Chrome/Edge/Opera bilan debugging qilsangiz, erta-kech bu bilan uchrashishingiz mumkin.

Bu debugger dagi xato emas, balki V8 ning maxsus xususiyati. Ehtimol, u qachondir o’zgartiriladi. Siz har doim ushbu sahifadagi misollarni ishga tushirish orqali buni tekshirishingiz mumkin.

Vazifalar

SayHi funktsiyasi tashqi o’zgaruvchi nomidan foydalanadi. Funktsiya ishga tushganda, u qaysi qiymatdan foydalanadi?

let name = "Jon";

funksiya sayHi() {
alert("Salom,"+ism);
}

ism = "Pit";

sayHi(); // u nimani ko'rsatadi: "Jon" yoki "Pit"?

Bunday holatlar brauzerda ham, server tomonida ham keng tarqalgan. Funksiya yaratilganidan keyinroq, masalan, foydalanuvchi harakati yoki tarmoq soʻrovidan keyin bajarilishi rejalashtirilishi mumkin.

Demak, savol tug’iladi: u so’nggi o’zgarishlarni oladimi?

Javob: Pit.

Funktsiya tashqi o’zgaruvchilarni hozirgi kabi oladi, u eng so’nggi qiymatlardan foydalanadi.

Eski o’zgaruvchilar qiymatlari hech qayerda saqlanmaydi. Agar funktsiya o’zgaruvchini xohlasa, u joriy qiymatni o’zining leksik muhitidan yoki tashqi muhitdan oladi.

Quyidagi makeWorker funktsiyasi boshqa funktsiya yaratadi va uni qaytaradi. O’sha yangi funktsiya boshqa joydan chaqirilishi mumkin.

U o’zining yaratilgan joyidagi tashqi o’zgaruvchilarga, yoki chaqirilgan joyidagiga, yoki ikkala joyidagiga ham kirish huquqiga ega bo’ladimi?

function makeWorker() {
  let name = "Pete";

  return function () {
    alert(name);
  };
}

let name = "John";

// funktsiya yaratish
let work = makeWorker();

// uni chaqirish
work(); // nima ko'rsatadi?

Qaysi qiymatni ko’rsatadi? “Pete” yoki “John”?

Javob: Pit.

Quyidagi koddagi “work()” funksiyasi tashqi leksik muhit havolasi orqali kelib chiqqan joydan “nom” oladi:

Demak, natija bu erda "Pit".

Ammo agar makeWorker() da let name bo’lmasa, qidiruv tashqariga chiqib, yuqoridagi zanjirdan ko’rib turganimizdek global o’zgaruvchini oladi. Bunday holda, natija “Jon” bo’ladi.

Bu erda bizda ikkita hisoblagich bor: makeCounter funktsiyasidan foydalangan holda counter va counter2.

Ular mustaqil emasmi? Ikkinchi hisoblagich nimani namoyish etadi? 0,1 yoki2,3 yoki boshqa narsa?

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();
let counter2 = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1

alert( counter2() ); // ?
alert( counter2() ); // ?

Javob: 0,1.

counter va counter2 funktsiyalari makeCounter ning turli xil chaqiruvlari bilan yaratilgan.

Shunday qilib, ular mustaqil tashqi leksik muhitlarga ega, ularning har biri o’ziga xos counter ga ega.

Bu yerda konstruktor funktsiyasi yordamida hisoblagich obyekti yaratildi.

Ishlaydimi? Bu nimani ko’rsatadi?

function Counter() {
  let count = 0;

  this.up = function () {
    return ++count;
  };
  this.down = function () {
    return --count;
  };
}

let counter = new Counter();

alert(counter.up()); // ?
alert(counter.up()); // ?
alert(counter.down()); // ?

Albatta, bu juda yaxshi ishlaydi.

Ikkala ichki funktsiyalar bir xil tashqi leksik muhitda yaratilgan, shuning uchun ular bir xil count o’zgaruvchaniga kirish huquqini ulashishadi:

function Counter() {
  let count = 0;

  this.up = function() {
    return ++count;
  };

  this.down = function() {
    return --count;
  };
}

let counter = new Counter();

alert( counter.up() ); // 1
alert( counter.up() ); // 2
alert( counter.down() ); // 1

Kodga qarang. So’nggi satrdagi chaqiruv natijasi qanday bo’ladi?

let phrase = "Hello";

if (true) {
  let user = "John";

  function sayHi() {
    alert(`${phrase}, ${user}`);
  }
}

sayHi();

Natijada xato bo’ladi.

sayHi funktsiyasi if ichida e’lon qilinadi, shuning uchun u faqat uning ichida yashaydi. Tashqarida sayHi yo’q.

Quyidagi kabi ishlaydigan sum funktsiyasini yozing: sum(a)(b) = a + b.

Ha, aynan shu tarzda, ikki qavsli qavslardan foydalanib (xato emas).

Masalan:

sum(1)(2) = 3
sum(5)(-1) = 4

Ikkinchi qavsning ishlashi uchun birinchisi funktsiyani qaytarishi kerak.

Shunga o’xshash:

function sum(a) {
  return function (b) {
    return a + b; // tashqi leksik muhitdan "a" ni oladi
  };
}

alert(sum(1)(2)); // 3
alert(sum(5)(-1)); // 4

Ushbu kodning natijasi qanday bo’ladi?

let x = 1;

function func() {
  console.log(x); // ?

  let x = 2;
}

func();

P.S. Bu vazifada tuzoq bor. Yechim ayniq emas.

Natija: xato.

Buni ishga tushirib ko’ring:

let x = 1;

function func() {
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
  let x = 2;
}

func();

Ushbu misolda biz “mavjud bo’lmagan” va “ishga tushirilmagan” o’zgaruvchi o’rtasidagi g’alati farqni kuzatishimiz mumkin.

O'zgaruvchi doirasi, yopilish (closure) maqolasida o’qiganimizdek, o’zgaruvchi ijro kod blokiga (yoki funktsiyaga) kirgandan boshlab “ishga tushirilmagan” holatda boshlanadi. Va u tegishli let ifodagacha ishga tushirilmagan qoladi.

Boshqacha qilib aytganda, o’zgaruvchi texnik jihatdan mavjud, lekin let dan oldin ishlatilmaydi.

Yuqoridagi kod buni ko’rsatadi.

function func() {
  // mahalliy x o'zgaruvchisi funktsiya boshidanoq mexanizmga ma'lum,
  // lekin let gacha "ishga tushirilmagan" (foydalanib bo'lmaydigan) ("o'lik zona")
  // shuning uchun xato

  console.log(x); // ReferenceError: Cannot access 'x' before initialization

  let x = 2;
}

O’zgaruvchining vaqtinchalik foydalanib bo’lmaydigan bu zonasi (kod bloki boshidan let gacha) ba’zan “o’lik zona” (dead zone) deb ataladi.

Bizda massivlar uchun o’rnatilgan arr.filter(f) usuli mavjud. U barcha elementlarni f funktsiyasi orqali filtrlaydi. Agar u true ni qaytarsa, u holda element olingan massivda qaytariladi.

“Foydalanishga tayyor” filtrlar to’plamini yarating:

  • inBetween(a, b)a va b o’rtasida yoki ularga teng (shu jumladan).
  • inArray([...]) – berilgan massivda.

Foydalanish shunday bo’lishi kerak:

  • arr.filter(inBetween(3,6)) – faqat 3 dan 6 gacha bo’lgan qiymatlarni tanlaydi.
  • arr.filter(inArray([1,2,3])) – faqat [1,2,3] a’zolaridan biriga mos elementlarni tanlaydi.

Masalan:

/* .. sizning kodingiz inBetween va inArray uchun */
let arr = [1, 2, 3, 4, 5, 6, 7];

alert(arr.filter(inBetween(3, 6))); // 3,4,5,6

alert(arr.filter(inArray([1, 2, 10]))); // 1,2

Sinovlar bilan sandbox-ni oching.

Filtr inBetween

function inBetween(a, b) {
  return function (x) {
    return x >= a && x <= b;
  };
}

let arr = [1, 2, 3, 4, 5, 6, 7];
alert(arr.filter(inBetween(3, 6))); // 3,4,5,6

Filtr inArray

function inArray(arr) {
  return function (x) {
    return arr.includes(x);
  };
}

let arr = [1, 2, 3, 4, 5, 6, 7];
alert(arr.filter(inArray([1, 2, 10]))); // 1,2

Yechimni sandbox-dagi sinovlar bilan oching.

Bizda tartiblash uchun bir massiv obyektlar mavjud:

let users = [
  { name: "John", age: 20, surname: "Johnson" },
  { name: "Pete", age: 18, surname: "Peterson" },
  { name: "Ann", age: 19, surname: "Hathaway" },
];

Buning bajarish uchun odatiy usul:

// nom bo'yicha (Ann, John, Pete)
users.sort((a, b) => (a.name > b.name ? 1 : -1));

// yosh bo'yicha (Pete, Ann, John)
users.sort((a, b) => (a.age > b.age ? 1 : -1));

Buni biz bundan ham qisqaroq qila olamizmi?

users.sort(byField("name"));
users.sort(byField("age"));

Shunday qilib, funktsiyani yozish o’rniga byField(fieldName) ni qo’ying.

Buning uchun ishlatilishi mumkin bo’lgan byField funktsiyasini yozing.

Sinovlar bilan sandbox-ni oching.

function byField(fieldName) {
  return (a, b) => (a[fieldName] > b[fieldName] ? 1 : -1);
}

Yechimni sandbox-dagi sinovlar bilan oching.

Quyidagi kod shooters massivini yaratadi.

Har bir funktsiya o’z raqamini chiqarishi kerak. Lekin nimadir noto’g’ri…

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function() { // shooter funktsiyasini yaratish,
      alert( i ); // o'z raqamini ko'rsatishi kerak
    };
    shooters.push(shooter); // uni massivga qo'shish
    i++;
  }

  // ...va shooterlar massivini qaytarish
  return shooters;
}

let army = makeArmy();

// barcha shooterlar o'z raqamlari 0, 1, 2, 3... o'rniga 10 ni ko'rsatadi
army[0](); // 0-raqamli shooterdan 10
army[1](); // 1-raqamli shooterdan 10
army[2](); // 10 ...va hokazo.

Nima uchun barcha shooterlar bir xil qiymatni ko’rsatadi?

Kod mo’ljallangandek ishlashi uchun uni tuzating.

Sinovlar bilan sandbox-ni oching.

makeArmy ichida aynan nima sodir bo’lishini ko’rib chiqaylik, va yechim aniq bo’lib qoladi.

  1. U bo’sh shooters massivini yaratadi:

    let shooters = [];
  2. Sikl orqali shooters.push(function) yordamida uni funktsiyalar bilan to’ldiradi.

    Har bir element funktsiya, shuning uchun natijada olingan massiv shunday ko’rinadi:

    shooters = [
      function () {
        alert(i);
      },
      function () {
        alert(i);
      },
      function () {
        alert(i);
      },
      function () {
        alert(i);
      },
      function () {
        alert(i);
      },
      function () {
        alert(i);
      },
      function () {
        alert(i);
      },
      function () {
        alert(i);
      },
      function () {
        alert(i);
      },
      function () {
        alert(i);
      },
    ];
  3. Massiv funktsiyadan qaytariladi.

    Keyin, keyinroq, istalgan a’zoga chaqiruv, masalan army[5]() massivdan army[5] elementini oladi (bu funktsiya) va uni chaqiradi.

    Endi nega bunday barcha funktsiyalar bir xil qiymatni, ya’ni 10 ni ko’rsatadi?

    Bu shooter funktsiyalari ichida mahalliy i o’zgaruvchisi yo’qligi sababli. Bunday funktsiya chaqirilganda, u i ni o’zining tashqi leksik muhitidan oladi.

    U holda i ning qiymati nima bo’ladi?

    Agar manbaga qarasak:

    function makeArmy() {
      ...
      let i = 0;
      while (i < 10) {
        let shooter = function() { // shooter funktsiyasi
          alert( i ); // o'z raqamini ko'rsatishi kerak
        };
        shooters.push(shooter); // funktsiyani massivga qo'shish
        i++;
      }
      ...
    }

    Ko’ramizki, barcha shooter funktsiyalar makeArmy() funktsiyasining leksik muhitida yaratilgan. Lekin army[5]() chaqirilganda, makeArmy allaqachon o’z ishini tugatgan va i ning oxirgi qiymati 10 (while i=10 da to’xtaydi).

    Natijada, barcha shooter funktsiyalar tashqi leksik muhitdan bir xil qiymatni oladi va bu oxirgi qiymat, ya’ni i=10.

    Yuqorida ko’rib turganingizdek, while {...} blokining har bir iteratsiyasida yangi leksik muhit yaratiladi. Shuning uchun, buni tuzatish uchun i ning qiymatini while {...} bloki ichidagi o’zgaruvchiga nusxalashimiz mumkin:

    function makeArmy() {
      let shooters = [];
    
      let i = 0;
      while (i < 10) {
          let j = i;
          let shooter = function() { // shooter funktsiyasi
            alert( j ); // o'z raqamini ko'rsatishi kerak
          };
        shooters.push(shooter);
        i++;
      }
    
      return shooters;
    }
    
    let army = makeArmy();
    
    // Endi kod to'g'ri ishlaydi
    army[0](); // 0
    army[5](); // 5

    Bu yerda let j = i “iteratsiya-mahalliy” j o’zgaruvchisini e’lon qiladi va i ni unga nusxalaydi. Primitivlar “qiymat bo’yicha” nusxalanadi, shuning uchun biz aslida joriy sikl iteratsiyasiga tegishli i ning mustaqil nusxasini olamiz.

    Shooterlar to’g’ri ishlaydi, chunki i ning qiymati endi bir oz yaqinroq joyda yashaydi. makeArmy() Leksik muhitida emas, balki joriy sikl iteratsiyasiga mos keladigan Leksik muhitda:

    Agar boshidayoq for ishlatganimizda ham bunday muammoning oldini olish mumkin edi:

    function makeArmy() {
    
      let shooters = [];
    
      for(let i = 0; i < 10; i++) {
        let shooter = function() { // shooter funktsiyasi
          alert( i ); // o'z raqamini ko'rsatishi kerak
        };
        shooters.push(shooter);
      }
    
      return shooters;
    }
    
    let army = makeArmy();
    
    army[0](); // 0
    army[5](); // 5

    Bu mohiyatan bir xil, chunki for har bir iteratsiyada o’zining i o’zgaruvchisi bilan yangi leksik muhit yaratadi. Shuning uchun har bir iteratsiyada yaratilgan shooter aynan shu iteratsiyadagi o’z i siga havola qiladi.

Endi, siz buni o’qishga shuncha harakat qilganingizdan so’ng va oxirgi retsept shuncha oddiy – shunchaki for dan foydalaning, siz hayron bo’lishingiz mumkin – bunga arziydimi?

Albatta, agar siz savolga osonlik bilan javob bera olsangiz, yechimni o’qimagan bo’lardingiz. Shuning uchun, umid qilamanki, bu vazifa sizga narsalarni biroz yaxshiroq tushunishga yordam bergan bo’lishi kerak.

Bundan tashqari, haqiqatan ham for dan ko’ra while ni afzal ko’radigan holatlar va bunday muammolar haqiqiy bo’lgan boshqa stsenariylar mavjud.

Yechimni sandbox-dagi sinovlar bilan oching.

O'quv qo'llanma xaritasi

Izohlar

izoh berishdan oldin buni o'qing…
  • Agar sizda nimani yaxshilash kerakligi haqida takliflaringiz bo'lsa - iltimos, GitHub muammosini yuboring yoki izoh berish o'rniga so'rov yuboring.
  • Agar siz maqolada biror narsani tushunolmasangiz - iltimos, batafsilroq ma'lumot bering.
  • Bir nechta so'z so'zlarini kiritish uchun <code> yorlig'ini ishlating, bir nechta satrlar uchun - ularni <pre> yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen…)