- Sözcüksel kapsamı, kapsam zincirini, blok kapsamını ve yukarı taşımayı (hoisting) açıklamak
- Gizli durum saklamak için closure'ları, IIFE'yi ve modül desenini kullanmak
- Bir çağrıda
this'in ne olacağını tahmin etmek ve onubind,call,applyya da ok fonksiyonuyla düzeltmek
Bir sitede kaç kez basıldığını hatırlaması gereken bir “Beğen” düğmesi var. Sayacı global bir değişkende tutarsan başka herhangi bir kod onu kazara bozabilir. Peki bir fonksiyon bittikten sonra kendi değişkenini nasıl “hatırlar”? Cevap closure'dır (kapanım): JavaScript'in en güçlü fikirlerinden biri ve mülakatlarda en sık sorulan konulardan biri.
Sözcüksel kapsam ve kapsam zinciri
Kapsam (scope), bir değişkenin görünür olduğu kod bölümüdür. JavaScript sözcüksel (lexical) kapsam kullanır: bir fonksiyonun hangi değişkenleri gördüğü, çağrıldığı yere değil, yazıldığı yere bağlıdır. Her fonksiyon ve let/const içeren her { } bloğu yeni bir kapsam oluşturur. JavaScript bir değişkeni ararken önce o anki kapsama, sonra dıştakine bakar ve global kapsama kadar gider; buna kapsam zinciri denir. Arama yalnızca içeriden dışarıya doğru yapılır, tersi asla.
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);▸ Beklenen çıktı
Nizami Nasimi Baku undefined undefined var ignores blocks
inner üç kapsamın hepsini görür, outer ise street'i göremez. var bloktan “kaçar”, let ise bloğun içinde kalır.Motor, kodu çalıştırmadan önce tüm bildirimleri kaydeder; buna yukarı taşıma (hoisting) denir. var ile bildirilen bir değişken, undefined değeriyle kapsamının en üstüne “taşınır”. Bir function bildirimi tamamen taşınır; bu yüzden fonksiyonu yazıldığı satırdan önce çağırabilirsin. let ve const da taşınır, ama kendi satırlarına kadar geçici ölü bölgede (TDZ) kalırlar: onlara erken erişmek ReferenceError fırlatır.
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';
}▸ Beklenen çıktı
undefined ReferenceError Function declarations are hoisted
Closure'lar
Bir fonksiyon ile oluşturulduğu kapsamdaki değişkenlerin birlikteliği. Dış fonksiyon bitse bile iç fonksiyon bu değişkenlere erişimini korur.
Normalde bir fonksiyon bitince yerel değişkenleri yok olur. Ama onları kullanan bir iç fonksiyon döndürülmüş ya da bir yere kaydedilmişse motor onları yaşatır. Dış fonksiyonun her çağrısı yeni ve ayrı bir değişken takımı oluşturur; aşağıda likes ile views birbirini hiç etkilemez.
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);▸ Beklenen çıktı
2 101 undefined
count nesnenin bir özelliği değildir; yalnızca closure içinde yaşar, bu yüzden dışarıdan değiştirilemez.Closure'ların sık görülen bir başka kullanımı fonksiyon fabrikalarıdır: bir parametreyi “hatırlayan” yeni fonksiyonlar üretirsin.
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));▸ Beklenen çıktı
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);
}▸ Beklenen çıktı
var 4 var 4 var 4 let 1 let 2 let 3
IIFE ve modül deseni
ES modüllerinden önce geliştiriciler değişkenleri bir IIFE ile gizlerdi: hemen çağrılan fonksiyon ifadesi (Immediately Invoked Function Expression), yani paranteze alınıp hemen çağrılan bir fonksiyon. Onun değişkenleri global kapsama “sızmaz”. IIFE'yi bir closure ile birleştirirsen modül desenini elde edersin: gizli veriler ve yalnızca seçtiğin metotları içeren açık bir nesne (API). Bugün dosya düzeyinde bu işi ES modülleri yapıyor, ama desen eski kodlarda hâlâ yaygın ve tek bir dosyanın içinde de işe yarar.
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);▸ Beklenen çıktı
OK Not enough money
{ balance: 70, history: [ '+100', '-30' ] }
undefinedbalance ve history yalnızca deposit, withdraw ve report aracılığıyla değişir. report, geçmişin dışarıdan bozulmaması için dizinin bir kopyasını döndürür.this, bind, call ve apply
Sıradan değişkenlerin aksine this sözcüksel değildir: normal bir fonksiyonda değeri, fonksiyonun nasıl çağrıldığına bağlıdır ve çağrı anında belirlenir. Ok fonksiyonlarının kendi this'i yoktur; onu çevredeki koddan alırlar.
| Çağrı | this nedir |
|---|---|
obj.method() | noktanın solundaki nesne — obj |
fn() | katı modda (sınıflar, modüller) undefined, eski katı olmayan kodda global nesne |
new Fn() | yeni oluşturulan nesne |
fn.call(obj, a, b), fn.apply(obj, [a, b]) | obj — fonksiyon hemen çağrılır |
fn.bind(obj) | this'i her zaman obj olan yeni bir fonksiyon |
| ok fonksiyonu | çevredeki 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]));▸ Beklenen çıktı
Hi, I am Aysel! Hello, I am Murad. Hey, I am Aysel!! 9
call argümanları tek tek, apply ise dizi olarak alır. bind fonksiyonu çağırmaz; this'i (istersen ilk argümanları da) sabitlenmiş yeni bir fonksiyon döndürür.Bir metodu callback olarak verdiğinde metot nesnesinden “kopar”. Sınıf metotları her zaman katı modda çalışır; bu yüzden this undefined olur ve bir özelliği okumak TypeError fırlatır. Çözümler: bind, bir ok fonksiyonu sarmalayıcısı ya da sınıf alanı olarak yazılmış bir ok fonksiyonu.
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);▸ Beklenen çıktı
Lost this: TypeError 5
createIdGenerator(prefix) fonksiyonunu yaz: her çağrıda bir sonraki kimliği veren bir fonksiyon döndürsün: user-1, user-2… Sayacı bir closure içinde tut ve her üretecin kendi sayacı olsun.
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());▸ Beklenen çıktı
user-1 user-2 order-1 user-3
finalPrice fonksiyonu indirimi this.discount'tan alır. 1) call ile shop nesnesi için 200 manatlık bir ürünün fiyatını yazdır. 2) bind ile shop'a bağlı bir fonksiyon oluştur ve [50, 80] fiyatlarını dönüştürmek için onu map'e ver.
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]▸ Beklenen çıktı
180 [ 45, 72 ]
Önemli noktalar
- Sözcüksel kapsam: fonksiyon yazıldığı yerin değişkenlerini görür; arama kapsam zinciri boyunca dışarıya doğru ilerler.
let/constblok kapsamlıdır ve kendi satırlarına kadar TDZ'dedir;varfonksiyon kapsamlıdır veundefinedolarak taşınır.- Closure, bir fonksiyon ve hatırladığı değişkenlerdir; dış fonksiyonun her çağrısı yeni bir takım oluşturur.
- IIFE ve modül deseni gizli verileri saklar ve yalnızca açık bir API döndürür.
thisçağrılma biçimine bağlıdır:call/applyonu bir kez verir,bindkalıcı olarak sabitler, ok fonksiyonu ise dışarıdan alır.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.
function outer() { let n = 0; return () => ++n; } veriliyor. const a = outer(); const b = outer(); a(); a(); satırlarından sonra b() ne döndürür?