JavaScript Closure Nedir?
JavaScript'in en çok sorulan mülakat konusu: closure nedir, nasıl çalışır, hangi problemi çözer. Gerçek dünya benzetmeleri, 5 kod örneği, mülakat SSS ve sık yapılan hatalar.
Closetrafi dolu, once onleri bosaltalim
JavaScript ogrenmeye yeni baslayan veya 2-3 yildir yazan ama temel kavramlari tam oturtamamis herkes, "closure" denince bir tedirginlik yasar. Bunun sebebi, konunun etrafinda gereksiz bir "gizem" olusturulmus olmasi. Aslinda closure basit bir sey — hayatinda bir kez anladin mi bir daha unutmuyorsun.
Yaygin yanlis inanclar:
- "Closure super karmasik bir sey" — degil, bir kapsam + bir fonksiyon, o kadar.
- "Sadece ileri seviye JavaScript'te kullanilir" — her yerde, en basit callback'lerde bile var.
- "Performansi cok yer" — cogu durumda motor optimize eder, dert etmeye gerek yok.
- "Mulakatta sorulmaz" — JavaScript mulakatinin %80'inde ilk 3 sorudan biri.
Bu yazi, closure'i 12 dakikada "bir daha unutmayacagin" sekilde anlatmak icin yazildi. Somut ornekler, benzetmeler ve mulakat SSS ile.
Benzetme: Çantaya not koymak
Bir restoran garsonusunu dusun. Masaya gelir, siparisi alir, mutfaga goturur, yemegi getirir. Masanin siparisini ezberinde tutar — cunku musteri hâlâ orada. Masanin siparisini "kafasi"nda tutan bu garson, aslinda bir closure: dis dunyadan (masa) bir parcayi iceride (kafasinda) tutuyor.
JavaScript'te ic fonksiyon bu garson. Dis fonksiyonun degiskenleri (count, name, vs.) o masa. Ic fonksiyon, dis fonksiyon "bitmis" gibi gorunse bile o degiskenleri canli tutmaya devam ediyor — ta ki kendisi de "emekli olana" kadar.
En basit kod örneği
Asagidaki kodu ac ve ne oldugunu tahmin et. Sonra calistir:
function disFonksiyon() {
const mesaj = "Merhaba";
function icFonksiyon() {
console.log(mesaj); // ← "mesaj" disFonksiyon'un degiskeni
}
return icFonksiyon;
}
const fn = disFonksiyon();
fn(); // Cikti: "Merhaba" 😮disFonksiyon() calistirildi ve bitti. mesaj degiskeni artik o scope'ta var olmamali — cunku fonksiyon dondu, scope temizlendi. Ama fn() cagirinca hâlâ "Merhaba" yazdiriyor.
Bu mumkun, cunku icFonksiyon, mesaj'i bir closure icinde "hapsediyor". JavaScript motoru, icFonksiyon hâlâ hayattayken o degiskeni garbage collector'a teslim etmiyor.
Sayaç (counter) — klasik örnek
Mulakatlarda en cok sorulan ornek: bir sayac olustur, disaridan count'a dogrudan erisilemesin, sadece "arttir" ve "oku" metodlari olsun. Class yazmadan yapabilir misin? Closure ile olur:
function sayacOlustur() {
let count = 0; // private degisken — disaridan erisilemez
return {
arttir: function() {
count += 1;
return count;
},
oku: function() {
return count;
}
};
}
const s = sayacOlustur();
console.log(s.oku()); // 0
console.log(s.arttir()); // 1
console.log(s.arttir()); // 2
console.log(s.oku()); // 2
// s.count ← undefined! Disaridan erisilemezBurada count degiskeni hicbir zaman dogrudan erisilebilir degil — sadece arttir() ve oku() fonksiyonlari uzerinden kontrol edilebilir. Bu encapsulation'in en sade hali, ve JavaScript'te class yazmadan mumkun.
"Class yazmadan private degisken nasil olusturulur?" sorusuna cevap: closure ile. Bu klasik JavaScript mulakat sorusudur, cevap bu sayfadaki sayac ornegidir.
Loop + setTimeout — sık yapılan hata
Asagidaki kodu calistir. Ne bekliyorsun, ne oluyor?
// YANLIS — var function-scoped
for (var i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, i * 100);
}
// Cikti: 4, 4, 4 ❌ (Beklenti: 1, 2, 3)Hep 4 yazdirmasinin sebebi: var i function-scoped, yani tum iterasyonlar ayni i degiskenini paylasiyor. setTimeout callback'i dongu bittikten sonra calistiginda i artik 4'e esit.
Cozum 1: let kullan (block-scoped, her iterasyon yeni i):
// DOGRU — let block-scoped
for (let i = 1; i <= 3; i++) {
setTimeout(function() {
console.log(i);
}, i * 100);
}
// Cikti: 1, 2, 3 ✓Cozum 2: forEach kullan (callback parametre olarak i alir):
// DOGRU — forEach
[1, 2, 3].forEach(function(i) {
setTimeout(function() {
console.log(i);
}, i * 100);
});
// Cikti: 1, 2, 3 ✓Cozum 3 (closure, eski usul): IIFE ile yeni kapsam:
// ESKI USUL — IIFE
for (var i = 1; i <= 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, j * 100);
})(i);
}
// Cikti: 1, 2, 3 ✓Mulakatta "neden hep 4 cikti?" diye sorarlarsa, "var function-scoped, tum iterasyonlar ayni i'yi paylasiyor, setTimeout dongu bittikten sonra calistigi icin i=4" cevabini ver. Pratikte bugun let veya forEach kullan — var ile dongu yazma.
Gerçek projede kullanım
Closure'i gercek projede 4 yerde gorursun. Bunlari bilmek mulakatta "evet kullaniyorum" demeni saglar.
1. React'te useState
useState bir closure — setter fonksiyonu, state'in onceki degerini 'gorur' ve gunceller. useEffect'in bagimlilik dizisi de closure yakalar.
2. Event handler'lar
Butona tiklandiginda calisan fonksiyon, tanimlandigi scope'taki degiskenlere erisir. En klasik closure kullanimi.
3. Module pattern (ES module oncesi)
IIFE ile modul sarmalayip, dis dunyaya sadece belirli API'ler acardik. Bugun ES module var ama mantik ayni: private state, public API.
4. Debounce / throttle
Bir fonksiyonu her seferinde cagirmak yerine, belli bir sure bekleyip son cagiriyi yapan yapilar closure ile yapilir (orn: lodash.debounce, lodash.throttle).
function debounce(fn, ms) {
let timeoutId; // closure'da tutuluyor
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
}
const aramaYap = debounce(function(query) {
console.log("Araniyor:", query);
}, 300);
// Kullanicinin her tus vurusunda aramaYap('p'), aramaYap('py'), aramaYap('pyt')
// Sadece sonuncusu 300ms sonra calisir — oncekiler iptal olurBurada timeoutId her cagirilan debounce instance'i icin ayri tutuluyor. Closure, ayni instance'in farkli cagrilardaki timeout ID'lerini karistirmadan yonetmesini sagliyor.
Mulakat SSS
JavaScript closure nedir, en basit şekilde nasıl açıklanır?
Closure, bir fonksiyonun kendi kapsamı dışındaki değişkenlere, tanımlandığı ortamın hafızası hâlâ canlıyken erişebilmesidir. Yani iç fonksiyon, dış fonksiyonun değişkenlerini 'hatırlar' ve o değişkenler çöp toplayıcı tarafından silinmez. Bu, JavaScript'te fonksiyon birinci sınıf vatandaş (first-class citizen) olduğu için mümkün olur.
Closure ile kapsam (scope) arasındaki fark nedir?
Kapsam, değişkenin nerede erişilebilir olduğunu belirler (global, function, block). Closure ise bir fonksiyonun kapsamı dışındaki değişkenlere hâlâ referans tutabilmesini sağlar. İkisi farklı kavramlar: kapsam değişkenin erişim alanını, closure ise o erişimin zaman içinde nasıl korunduğunu tanımlar.
JavaScript'te en klasik closure örneği nedir?
Sayaç (counter) fonksiyonu: dış fonksiyon bir 'count' değişkeni tanımlar ve iç fonksiyonu döner. İç fonksiyon count'u artırır. count hiçbir yerden doğrudan erişilemez ama iç fonksiyon üzerinden değiştirilebilir. Bu 'private state' örüntüsüdür.
Loop içinde closure kullanırken sık yapılan hata nedir?
Klasik hata: 'var i' ile for döngüsü yazıp, içeride setTimeout veya event listener ile i'yi kullanmak. Hep aynı son değeri görürsün çünkü tüm closure'lar aynı 'i' değişkenine referans verir. Çözüm: 'let i' kullan (block scope, her iterasyonda yeni i) veya IIFE ile kapsam yarat.
Mulakat sorusu: setTimeout içinde neden hep aynı i çıkıyor?
Çünkü 'var' function-scoped, tüm iterasyonlar aynı 'i' değişkenini paylaşıyor. setTimeout callback'i döngü bittikten sonra çalışıyor ve o an i=5 (son değer). Çözüm: 'let' kullan (her iterasyon yeni 'i' oluşturur) veya 'forEach' kullan (callback parametre olarak 'i' alır, kapsam sorunu olmaz).
JavaScript'te closure performans açısından sorunlu mu?
Çoğu durumda hayır. Modern motorlar closure'ları optimize eder. Ancak çok büyük objeleri closure içinde tutarsan, o objeler garbage collector tarafından serbest bırakılmaz — memory leak riski olabilir. Pratikte çoğu closure kullanımı güvenlidir; dikkat etmen gereken yer: event listener'larda tutulan büyük veri yapıları.
Bir cümlede özet
Closure, JavaScript'in "bir fonksiyonu her şeyiyle hatırlayan bir nesne" gibi davranmasını sağlayan mekanizmadır.
Bunu bilmek mülakatta sana +5 puan kazandırır, gerçek projede debug yeteneğini 2x artırır. Sıradaki adım: React'te useState, useEffect gibi hook'ların neden closure kullandığını anlamak — aynı prensip.
Sıradaki adımlar
- Tarayıcıda
function disFonksiyon() {...}örneğini dene, mesaj'ın neden hâlâ erişilebilir olduğunu gözlemle - Sıfırdan Zirveye yazımızla programlama temellerini pekiştir
- Algoritma soruları ile closure gerektiren problemleri çöz
İlgili Yazılar
Closure öğrendin, şimdi diğerlerini de
JavaScript'in en çok sorulan mülakat konularını sırayla öğren: hoisting, this, prototype, async/await, event loop.