- Leksik görünmə sahəsini, sahələr zəncirini, blok sahəsini və qaldırılmanı (hoisting) izah etmək
- Gizli vəziyyət saxlamaq üçün qapanmalardan, IIFE-dən və modul şablonundan istifadə etmək
- Çağırışda
this-in nə olacağını təxmin etmək və onubind,call,applyvə ya ox funksiyası ilə düzəltmək
Saytda «Bəyən» düyməsi var və o, neçə dəfə basıldığını yadda saxlamalıdır. Sayğacı qlobal dəyişəndə saxlasan, istənilən başqa kod onu təsadüfən poza bilər. Bəs funksiya bitəndən sonra da öz dəyişənini necə «xatırlaya» bilər? Cavab qapanmadır (closure) — JavaScript-in ən güclü ideyalarından biri və müsahibələrdə ən çox soruşulan mövzulardan biri.
Leksik görünmə sahəsi və sahələr zənciri
Görünmə sahəsi kodun dəyişənin göründüyü hissəsidir. JavaScript leksik sahədən istifadə edir: funksiyanın hansı dəyişənləri gördüyü onun harada yazıldığından asılıdır, harada çağırıldığından yox. Hər funksiya, let/const olan hər { } bloku yeni sahə yaradır. Dəyişən axtarılanda JavaScript əvvəlcə cari sahəyə, sonra xarici sahəyə baxır və qlobal sahəyə qədər gedir — buna sahələr zənciri deyilir. Axtarış yalnız içəridən çölə gedir, əksinə heç vaxt.
const city = 'Baku';
function outer() {
const district = 'Nasimi';
function inner() {
const street = 'Nizami';
console.log(street, district, city);
}
inner();
console.log(typeof street);
}
outer();
if (true) {
let blockVar = 'inside';
var functionVar = 'var ignores blocks';
}
console.log(typeof blockVar, functionVar);▸ Gözlənilən nəticə
Nizami Nasimi Baku undefined undefined var ignores blocks
inner üç sahənin hamısını görür, outer isə street-i görmür. var blokdan «qaçır», let isə blokun içində qalır.Kod icra olunmazdan əvvəl mühərrik bütün elanları qeydə alır — buna qaldırılma (hoisting) deyilir. var ilə elan olunmuş dəyişən sahənin əvvəlinə «qaldırılır» və undefined qiyməti alır. function elanı bütövlükdə qaldırılır, ona görə onu yazıldığı yerdən əvvəl çağırmaq olar. let və const da qaldırılır, amma öz sətrinə qədər müvəqqəti ölü zonada (TDZ) qalır: onlara vaxtından əvvəl müraciət ReferenceError verir.
console.log(early);
var early = 'var is hoisted';
try {
console.log(later);
} catch (error) {
console.log(error.name);
}
let later = 'let waits in the TDZ';
console.log(sayHi());
function sayHi() {
return 'Function declarations are hoisted';
}▸ Gözlənilən nəticə
undefined ReferenceError Function declarations are hoisted
Qapanmalar
Funksiya ilə onun yarandığı sahədəki dəyişənlərin birliyi. Xarici funksiya işini bitirsə belə, daxili funksiya həmin dəyişənlərə çıxışı saxlayır.
Adətən funksiya bitəndə onun lokal dəyişənləri yox olur. Amma həmin dəyişənlərdən istifadə edən daxili funksiya qaytarılıbsa və ya harasa yazılıbsa, mühərrik onları yaşadır. Xarici funksiyanın hər çağırışı yeni, ayrıca dəyişənlər dəsti yaradır — aşağıda likes və views bir-birinə mane olmur.
function createCounter(start = 0) {
let count = start;
return {
increment: () => ++count,
decrement: () => --count,
value: () => count,
};
}
const likes = createCounter();
const views = createCounter(100);
likes.increment();
likes.increment();
views.increment();
console.log(likes.value(), views.value());
console.log(likes.count);▸ Gözlənilən nəticə
2 101 undefined
count obyektin xassəsi deyil — o, yalnız qapanmada yaşayır, ona görə kənardan onu dəyişmək mümkün deyil.Qapanmaların başqa tez-tez rast gəlinən istifadəsi fabrik funksiyalardır: parametri «yadda saxlayan» yeni funksiyalar yaradırsan.
function makeMultiplier(factor) {
return (x) => x * factor;
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(5), triple(5));
console.log([1, 2, 3].map(triple));▸ Gözlənilən nəticə
10 15 [ 3, 6, 9 ]
for (var i = 1; i <= 3; i++) {
setTimeout(() => console.log('var', i), 0);
}
for (let j = 1; j <= 3; j++) {
setTimeout(() => console.log('let', j), 0);
}▸ Gözlənilən nəticə
var 4 var 4 var 4 let 1 let 2 let 3
IIFE və modul şablonu
ES modullarından əvvəl proqramçılar dəyişənləri IIFE ilə gizlədirdilər — dərhal çağırılan funksiya ifadəsi (Immediately Invoked Function Expression): mötərizəyə alınmış və elə oradaca çağırılan funksiya. Onun dəyişənləri qlobal sahəyə «sızmır». IIFE qapanma ilə birləşəndə modul şablonu alınır: gizli verilənlər və yalnız lazım olan metodları olan açıq obyekt (API). Bu gün fayllar səviyyəsində bu işi ES modulları görür, amma şablon köhnə kodda hələ də geniş yayılıb və bir faylın içində faydalıdır.
const bank = (function () {
let balance = 0;
const history = [];
return {
deposit(amount) {
balance += amount;
history.push(`+${amount}`);
},
withdraw(amount) {
if (amount > balance) return 'Not enough money';
balance -= amount;
history.push(`-${amount}`);
return 'OK';
},
report: () => ({ balance, history: [...history] }),
};
})();
bank.deposit(100);
console.log(bank.withdraw(30), bank.withdraw(500));
console.log(bank.report());
console.log(typeof balance);▸ Gözlənilən nəticə
OK Not enough money
{ balance: 70, history: [ '+100', '-30' ] }
undefinedbalance və history yalnız deposit, withdraw və report vasitəsilə dəyişir. report massivin surətini qaytarır ki, kənardan tarixçəni pozmaq olmasın.this, bind, call və apply
Adi dəyişənlərdən fərqli olaraq this leksik deyil: adi funksiyada onun qiyməti funksiyanın necə çağırıldığından asılıdır və çağırış anında müəyyən olunur. Ox funksiyalarının isə öz this-i yoxdur — onlar this-i ətrafdakı koddan götürür.
| Çağırış | this nədir |
|---|---|
obj.method() | nöqtədən soldakı obyekt — obj |
fn() | sərt rejimdə (siniflər, modullar) undefined, köhnə qeyri-sərt kodda qlobal obyekt |
new Fn() | yeni yaradılan obyekt |
fn.call(obj, a, b), fn.apply(obj, [a, b]) | obj — funksiya dərhal çağırılır |
fn.bind(obj) | this-i həmişəlik obj olan yeni funksiya |
| ox funksiyası | ətrafdakı kodun this-i |
function introduce(greeting, punctuation) {
return `${greeting}, I am ${this.name}${punctuation}`;
}
const aysel = { name: 'Aysel' };
const murad = { name: 'Murad' };
console.log(introduce.call(aysel, 'Hi', '!'));
console.log(introduce.apply(murad, ['Hello', '.']));
const ayselIntro = introduce.bind(aysel, 'Hey');
console.log(ayselIntro('!!'));
console.log(Math.max.apply(null, [3, 9, 4]));▸ Gözlənilən nəticə
Hi, I am Aysel! Hello, I am Murad. Hey, I am Aysel!! 9
call arqumentləri tək-tək, apply massiv kimi alır. bind funksiyanı çağırmır — this-i (və istəsən, ilk arqumentləri) bağlanmış yeni funksiya qaytarır.Metodu callback kimi ötürəndə o, obyektindən «qopur». Sinif metodları həmişə sərt rejimdə işləyir, ona görə this undefined olur və xassəni oxumaq TypeError verir. Çarələr: bind, ox funksiyası ilə sarğı və ya sinif sahəsi kimi yazılmış ox funksiyası.
class Player {
score = 0;
addPoint() {
this.score++;
}
addPointArrow = () => {
this.score++;
};
}
const player = new Player();
const detached = player.addPoint;
try {
detached();
} catch (error) {
console.log('Lost this:', error.name);
}
[1, 2].forEach(player.addPoint.bind(player));
[1, 2, 3].forEach(player.addPointArrow);
console.log(player.score);▸ Gözlənilən nəticə
Lost this: TypeError 5
createIdGenerator(prefix) funksiyasını yaz: o, hər çağırışda növbəti identifikatoru qaytaran funksiya qaytarsın: user-1, user-2... Sayğac qapanmada saxlanmalıdır və hər generatorun öz sayğacı olmalıdır.
function createIdGenerator(prefix) {
// keep a counter here
return () => {
// increase the counter and return `${prefix}-${count}`
};
}
const userId = createIdGenerator('user');
const orderId = createIdGenerator('order');
console.log(userId());
console.log(userId());
console.log(orderId());
console.log(userId());▸ Gözlənilən nəticə
user-1 user-2 order-1 user-3
finalPrice funksiyası endirimi this.discount-dan götürür. 1) call ilə shop obyekti üçün 200 manatlıq malın qiymətini çap et. 2) bind ilə shop-a bağlanmış funksiya yarat və onu map-ə ötürərək [50, 80] qiymətlərini çevir.
const shop = { discount: 10 };
function finalPrice(price) {
return price - (price * this.discount) / 100;
}
// 1) finalPrice for 200 with call
// 2) bind to shop and use it in map for [50, 80]▸ Gözlənilən nəticə
180 [ 45, 72 ]
Əsas fikirlər
- Leksik sahə: funksiya yazıldığı yerin dəyişənlərini görür, axtarış sahələr zənciri ilə içəridən çölə gedir.
let/constblok sahəlidir və öz sətrinə qədər TDZ-dədir;varfunksiya sahəlidir vəundefinedkimi qaldırılır.- Qapanma — funksiya və onun xatırladığı dəyişənlərdir; xarici funksiyanın hər çağırışı yeni dəst yaradır.
- IIFE və modul şablonu gizli verilənləri saxlayır və yalnız açıq API qaytarır.
thisçağırış üsulundan asılıdır:call/applyonu bir dəfəlik verir,bindhəmişəlik bağlayır, ox funksiyası çöldən götürür.
Özünü yoxla
10 sual. Hər düzgün cavab XP qazandırır.
function outer() { let n = 0; return () => ++n; } verilib. const a = outer(); const b = outer(); a(); a(); sətirlərindən sonra b() nə qaytarır?