Closure (kapanım), bir fonksiyonun tanımlandığı yerdeki değişkenlere, o yerin çalışması bittikten sonra bile erişebilmesidir. MDN'nin tanımıyla closure, bir fonksiyon ile çevresindeki durumun, yani sözcüksel ortamın (lexical environment) birlikte paketlenmesidir. JavaScript'te her fonksiyon oluşturulduğu anda bir closure da oluşur; bu yüzden closure özel bir sözdizimi değil, dilin doğal bir davranışıdır.
Bu yazıda closure'ı önce en basit hâliyle gösteriyoruz, sonra sayaçlar, gizli (private) durum, tek seferlik fonksiyonlar ve önbellekleme gibi pratik kullanımlara geçiyoruz. En sonda var ile döngü içinde closure oluştururken yaşanan klasik hatayı ve çözümlerini ele alıyoruz. Kapsam kurallarının kendisi için scope yazısını okumanızı öneririz.
İlk örnek: dışarıdaki değişkeni hatırlayan fonksiyon

Aşağıdaki carpanUret bir fonksiyon döndürür. Döndürülen fonksiyon carpan parametresini kullanır. carpanUret çağrısı bittiğinde carpan değişkeni kaybolmaz, çünkü iç fonksiyon ona referans tutar:
function carpanUret(carpan) {
return function (sayi) {
return sayi * carpan;
};
}
const ikiKat = carpanUret(2);
const ucKat = carpanUret(3);
console.log(ikiKat(5)); // 10
console.log(ucKat(5)); // 15ikiKat ve ucKat aynı fonksiyon gövdesini paylaşır, ama her biri ayrı bir sözcüksel ortam taşır: Birinde carpan 2, diğerinde 3'tür. MDN bu kalıba "fonksiyon fabrikası" (function factory) der. Bazı dillerde yerel değişkenler fonksiyonun çalışması bitince yok olur; JavaScript'te ise closure, ihtiyaç duyulan değişkenleri yaşatmaya devam eder.
Closure değeri değil değişkeni yakalar
Closure, değişkenin o anki değerinin bir kopyasını almaz; değişkenin kendisine bağlanır. Değişken sonradan değişirse closure yeni değeri görür:
let mesaj = "ilk";
const oku = () => mesaj;
mesaj = "son";
console.log(oku()); // sonBu ayrıntı, döngü hatasını anlamanın anahtarıdır. Birazdan göreceğiniz gibi sorun, birden fazla closure'ın aynı değişkeni paylaşmasından çıkar.
Pratik kullanım 1: bağımsız sayaçlar
Closure ile bir değişkeni yalnızca belirli fonksiyonların erişebileceği şekilde saklayabilirsiniz. sayacOlustur her çağrıldığında yeni bir sayi değişkeni ve ona erişen üç fonksiyon oluşturur:
function sayacOlustur() {
let sayi = 0;
return {
arttir() {
sayi++;
return sayi;
},
azalt() {
sayi--;
return sayi;
},
deger() {
return sayi;
},
};
}
const a = sayacOlustur();
const b = sayacOlustur();
a.arttir();
a.arttir();
b.arttir();
console.log(a.deger(), b.deger()); // 2 1
console.log(a.sayi); // undefinedİki sayaç birbirinden bağımsızdır, çünkü her sayacOlustur() çağrısı yeni bir sözcüksel ortam oluşturur. Aynı sayaca ait üç metot ise tek bir ortamı paylaşır. a.sayi ifadesinin undefined dönmesi, değişkenin nesnenin bir özelliği olmadığını, dışarıdan doğrudan okunamayacağını gösterir.
Pratik kullanım 2: gizli durum ve modül deseni

MDN, sınıflarda gerçek private alanlar gelmeden önce closure'ların gizli metotları taklit etmek için kullanıldığını anlatır. Aşağıdaki hesap örneğinde bakiye, hareketler ve kaydet dışarıdan görünmez; yalnızca döndürülen metotlar üzerinden kullanılabilir:
function hesapAc(bakiye) {
const hareketler = [];
function kaydet(tur, tutar) {
hareketler.push(`${tur} ${tutar}`);
}
return {
yatir(tutar) {
bakiye += tutar;
kaydet("yatırma", tutar);
return bakiye;
},
cek(tutar) {
if (tutar > bakiye) return "Yetersiz bakiye";
bakiye -= tutar;
kaydet("çekme", tutar);
return bakiye;
},
gecmis() {
return [...hareketler];
},
};
}
const hesap = hesapAc(100);
console.log(hesap.yatir(50)); // 150
console.log(hesap.cek(500)); // Yetersiz bakiye
console.log(hesap.cek(30)); // 120
console.log(hesap.gecmis()); // [ 'yatırma 50', 'çekme 30' ]
console.log(hesap.bakiye, typeof hesap.kaydet); // undefined undefinedgecmis() metodunun diziyi kopyalayarak döndürdüğüne dikkat edin. Diziyi doğrudan döndürseydiniz, dışarıdaki kod onu değiştirebilir ve gizliliği bozabilirdi. Aynı deseni bir IIFE ile tek bir nesne için de kurabilirsiniz; MDN bu yaklaşımı modül deseni (module pattern) olarak anar. Bugün aynı ihtiyaç için sınıflardaki private alanları ya da ES modüllerini de kullanabilirsiniz; modül kapsamındaki bir değişkeni dışa aktarılan fonksiyonlarla açmak da bir closure'dır.
Pratik kullanım 3: tek seferlik çalışma ve önbellek
Closure, bir fonksiyonu saran ve ona hafıza ekleyen yardımcılar yazmayı kolaylaştırır. İlk örnek, verilen fonksiyonu yalnızca bir kez çalıştırıp sonraki çağrılarda ilk sonucu döndürür:
function birKez(fn) {
let calisti = false;
let sonuc;
return function (...args) {
if (!calisti) {
calisti = true;
sonuc = fn(...args);
}
return sonuc;
};
}
const baslat = birKez((ad) => `${ad} başlatıldı`);
console.log(baslat("sunucu")); // sunucu başlatıldı
console.log(baslat("başka")); // sunucu başlatıldıİkinci örnek, hesaplanan sonuçları bir Map içinde saklayan basit bir önbellek (memoization) yardımcısıdır. Map hakkında ayrıntı için Map ve Set yazısına bakabilirsiniz:
function onbellekli(fn) {
const kayit = new Map();
return (n) => {
if (kayit.has(n)) return `${kayit.get(n)} (önbellek)`;
const sonuc = fn(n);
kayit.set(n, sonuc);
return sonuc;
};
}
const kareAl = onbellekli((n) => n * n);
console.log(kareAl(9)); // 81
console.log(kareAl(9)); // 81 (önbellek)Closure kapsam zinciri
İç içe fonksiyonlarda en içteki fonksiyon yalnızca bir üst kapsamı değil, tüm dış kapsamları görür. MDN bunu closure kapsam zinciri olarak anlatır. Aşağıda en içteki fonksiyon üç parametreye ve global e değişkenine erişiyor:
const e = 10;
function topla(a) {
return function (b) {
return function (c) {
return a + b + c + e;
};
};
}
console.log(topla(1)(2)(3)); // 16Klasik hata: var ile döngüde closure
let gelmeden önce en sık rastlanan closure hatası döngülerde ortaya çıkıyordu. var blok kapsamı oluşturmaz; döngü değişkeni tüm fonksiyona (ya da global kapsama) ait tek bir değişkendir. Döngüde oluşturulan bütün closure'lar bu tek değişkeni paylaşır ve çalıştıklarında döngü çoktan bitmiş olduğu için hepsi son değeri görür:
var islerVar = [];
for (var i = 0; i < 3; i++) {
islerVar.push(() => i);
}
console.log(islerVar.map((f) => f())); // [ 3, 3, 3 ]
const islerLet = [];
for (let j = 0; j < 3; j++) {
islerLet.push(() => j);
}
console.log(islerLet.map((f) => f())); // [ 0, 1, 2 ]Aynı durum zamanlayıcılarda da görülür; setTimeout callback'leri döngü bittikten sonra çalışır:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log("var:", i), 0);
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log("let:", j), 0);
}
// var: 3
// var: 3
// var: 3
// let: 0
// let: 1
// let: 2for başlığında let kullanıldığında her tur için ayrı bir değişken oluşur ve her closure kendi kopyasını yakalar. Eski kodlarda aynı sorun, her tur için yeni bir kapsam oluşturan bir IIFE ya da fonksiyon fabrikasıyla çözülürdü:
var islerIife = [];
for (var k = 0; k < 3; k++) {
islerIife.push(
(function (kopya) {
return () => kopya;
})(k),
);
}
console.log(islerIife.map((f) => f())); // [ 0, 1, 2 ]Performans ve bellek
Closure'lar ücretsiz değildir. Her fonksiyon örneği kendi kapsamını taşıdığı için, gerekmediği hâlde fonksiyon içinde fonksiyon oluşturmak MDN'ye göre hem işlem hızını hem de bellek kullanımını olumsuz etkileyebilir. Örneğin her nesne için kurucu içinde yeniden tanımlanan metotlar yerine, ortak davranışı prototip üzerinde bir kez tanımlamak genellikle daha uygundur. Ayrıca closure bir değişkene referans tuttuğu sürece o değişkenin işaret ettiği veri de bellekte kalır; büyük verileri gereksiz yere yakalamamaya dikkat edin. Konunun ayrıntıları bellek yönetimi ve garbage collection yazısında.
Sık yapılan hatalar

- Döngü içinde
varile closure oluşturup her closure'ın farklı değer göreceğini sanmak. - Closure'ın değişkenin değerini kopyaladığını düşünmek; aslında değişkenin kendisini paylaşır.
- Gizli tutulması gereken bir diziyi ya da nesneyi doğrudan döndürüp dışarıdan değiştirilmesine izin vermek.
- Her nesne için gereksiz yere yeni fonksiyonlar oluşturup belleği şişirmek.
- Arrow function'ın
thisdeğerini de closure ile yakaladığını unutmak; bu davranış this yazısında anlatılıyor.
Sık Sorulan Sorular
Her fonksiyon bir closure mudur?
MDN'ye göre JavaScript'te closure'lar her fonksiyon oluşturulduğunda, oluşturulma anında meydana gelir. Pratikte "closure" kelimesini genellikle dış kapsamdaki bir değişkeni kullanan ve o kapsamdan daha uzun yaşayan fonksiyonlar için kullanırız.
Closure ile sınıf arasında nasıl seçim yapmalıyım?
Tek bir davranış ve az sayıda gizli değişken için closure kısa ve okunaklıdır. Çok sayıda metot, kalıtım ya da çok sayıda örnek gerekiyorsa sınıf ve prototip daha uygun olabilir, çünkü metotlar örnekler arasında paylaşılır.
Closure blok kapsamındaki değişkenleri de yakalar mı?
Evet. Closure'lar fonksiyon kapsamının yanında let/const ile oluşan blok kapsamlarını ve modül kapsamını da yakalayabilir. Bloğun dışından değişkene doğrudan erişemezsiniz, ama blok içinde oluşturulan fonksiyon erişmeye devam eder.
Closure bellek sızıntısına yol açar mı?
Closure tek başına sızıntı değildir, ama gereğinden uzun yaşayan bir closure büyük bir veriye referans tutuyorsa o verinin serbest bırakılmasını engeller. İhtiyacınız bittiğinde closure'a olan referansları bırakmak bu riski azaltır.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: JavaScript öğrenme rehberi · Scope ve kapsam zinciri · Fonksiyon türleri · var, let ve const farkı · Callback fonksiyonlar · Bellek yönetimi