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.
let/const oâzgaruvchilar haqida gaplashamizJavaScript da oâzgaruvchi eâlon qilishning 3 xil usuli bor: let, const (zamonaviy usullar) va var (oâtmishdan qolgan).
- Ushbu maqolada biz misollarda
letoâzgaruvchilarini ishlatamiz. constbilan eâlon qilingan oâzgaruvchilar ham xuddi shunday harakat qiladi, shuning uchun bu maqolaconsthaqida ham.- Eski
varning 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);
}
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)
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:
- Environment Record â barcha mahalliy oâzgaruvchilarni oâz xususiyatlari sifatida saqlaydigan objekt (va
thisqiymati kabi boshqa maâlumotlar). - 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:
- 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
letbilan eâlon qilinmaguncha unga murojaat qilib boâlmaydi. Bu deyarli oâzgaruvchi mavjud emasdek.
- Dastlab ular âIshga tushirilmaganâ holatda. Bu maxsus ichki holat, yaâni mexanizm oâzgaruvchi haqida biladi, lekin u
- Keyin
let phrasetaârifi paydo boâladi. Hali tayinlash yoâq, shuning uchun uning qiymatiundefined. Shu nuqtadan boshlab oâzgaruvchini ishlatishimiz mumkin. phrasega qiymat tayinlanadi.phraseqiymatni 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: 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
sayning joriy ijrosiga mos keladi. Unda bitta xususiyat bor:name, funktsiya argumenti. Bizsay("John")ni chaqirdik, shuning uchunnamening qiymati"John". - Tashqi Leksik muhit global Leksik muhitdir. Unda
phraseoâ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:
nameoâzgaruvchisi uchunsayichidagialertuni darhol ichki Leksik muhitda topadi.phrasega kirmoqchi boâlganda, mahalliyphraseyoâ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â 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.
Izohlar
<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â¦)