İçeriğe geç
JavaScript

JavaScript Closures ve Scope: Kapsam Zinciri

Tarık Tunç
JavaScript Closures ve Scope: Kapsam Zinciri

JavaScript Closures ve Scope: Kapsam Zinciri

Closure, JavaScript'in en güçlü ve en çok yanlış anlaşılan kavramlarından biridir. Bir fonksiyonun, tanımlandığı kapsamdaki değişkenlere — o kapsam sona erdikten sonra bile — erişebilmesi anlamına gelir. Scope (kapsam) zinciri ile birlikte closure'lar, modül pattern'leri, veri gizleme ve fonksiyon fabrikaları gibi birçok ileri düzey tekniğin temelini oluşturur.

1. Scope (Kapsam) Türleri

JavaScript'te üç temel kapsam türü vardır: global kapsam, fonksiyon kapsamı ve blok kapsamı. Her kapsam, değişkenlerin erişilebilirlik sınırlarını belirler.

// Global kapsam
const globalDegisken = "Her yerden erişilebilir";

function disKapsam() {
  // Fonksiyon kapsamı
  const fonksiyonDegiskeni = "Sadece bu fonksiyonda erişilebilir";

  if (true) {
    // Blok kapsamı (let ve const)
    let blokDegiskeni = "Sadece bu blokta erişilebilir";
    var varDegiskeni = "Fonksiyon kapsamında erişilebilir (var)";

    console.log(globalDegisken);     // ✅ Erişilebilir
    console.log(fonksiyonDegiskeni); // ✅ Erişilebilir
    console.log(blokDegiskeni);      // ✅ Erişilebilir
  }

  console.log(varDegiskeni);     // ✅ var blok kapsamına uymaz
  // console.log(blokDegiskeni); // ❌ ReferenceError
}

// Kapsam zinciri arama sırası
const x = "global";

function dis() {
  const x = "dış fonksiyon";

  function ic() {
    // const x = "iç fonksiyon"; // Eğer burada tanımlı değilse...
    console.log(x); // "dış fonksiyon" — kapsam zincirinde yukarı arar
  }

  ic();
}

dis();

2. Closure'ın Temelleri

Bir fonksiyon, başka bir fonksiyonun içinde tanımlandığında, dış fonksiyonun değişkenlerine referans tutar. Dış fonksiyon tamamlansa bile bu referanslar korunur — işte closure budur.

// Temel closure örneği
function sayacOlustur() {
  let deger = 0; // Closure ile korunacak değişken

  return {
    artir() { return ++deger; },
    azalt() { return --deger; },
    degerGetir() { return deger; }
  };
}

const sayac = sayacOlustur();
console.log(sayac.artir());     // 1
console.log(sayac.artir());     // 2
console.log(sayac.azalt());     // 1
console.log(sayac.degerGetir()); // 1

// 'deger' değişkenine dışarıdan doğrudan erişim yok!
// console.log(deger); // ❌ ReferenceError

// Her çağrı yeni bir closure oluşturur
const sayac2 = sayacOlustur();
console.log(sayac2.degerGetir()); // 0 (bağımsız kapsam)
İpucu: Closure'lar değişkenlerin değerini değil, referansını tutar. Bu yüzden closure içinden değişkenin güncel değerine her zaman erişebilirsiniz ve değişiklikler closure dışından da gözlemlenebilir.

3. Closure Kullanım Alanları

Closure'lar birçok önemli programlama pattern'inin temelini oluşturur. İşte en yaygın kullanım senaryoları:

// 1. Veri gizleme (Encapsulation)
function oturumYoneticisi() {
  let token = null;
  let sonErisim = null;

  return {
    girisYap(yeniToken) {
      token = yeniToken;
      sonErisim = Date.now();
      console.log("Giriş başarılı");
    },
    tokenGetir() {
      if (!token) return null;
      sonErisim = Date.now();
      return token;
    },
    cikisYap() {
      token = null;
      sonErisim = null;
      console.log("Çıkış yapıldı");
    }
  };
}

// 2. Fonksiyon fabrikası
function carpanOlustur(carpan) {
  return function(sayi) {
    return sayi * carpan;
  };
}

const ikiKati = carpanOlustur(2);
const ucKati = carpanOlustur(3);
console.log(ikiKati(5));  // 10
console.log(ucKati(5));   // 15

// 3. Memoization (Önbellekleme)
function memoize(fn) {
  const onbellek = new Map();

  return function(...args) {
    const anahtar = JSON.stringify(args);

    if (onbellek.has(anahtar)) {
      console.log("Önbellekten döndürülüyor");
      return onbellek.get(anahtar);
    }

    const sonuc = fn.apply(this, args);
    onbellek.set(anahtar, sonuc);
    return sonuc;
  };
}

const fibonacci = memoize(function(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
});

console.log(fibonacci(40)); // Anında hesaplanır

// 4. Partial application
function partial(fn, ...oncekiArgs) {
  return function(...sonrakiArgs) {
    return fn(...oncekiArgs, ...sonrakiArgs);
  };
}

function log(seviye, tarih, mesaj) {
  console.log(`[${seviye}] ${tarih}: ${mesaj}`);
}

const bilgiLog = partial(log, "INFO");
const bugununLogu = partial(bilgiLog, new Date().toLocaleDateString());
bugununLogu("Sunucu başlatıldı"); // [INFO] 27.03.2026: Sunucu başlatıldı

4. Yaygın Closure Tuzakları

Closure'lar güçlü olsa da dikkatli kullanılmazsa beklenmeyen davranışlara neden olabilir.

// Tuzak 1: Döngülerde var ile closure
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Çıktı: 3, 3, 3 (hepsi aynı 'i'ye referans tutar)

// Çözüm 1: let kullanımı
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Çıktı: 0, 1, 2

// Çözüm 2: IIFE ile yeni kapsam
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 100);
  })(i);
}
// Çıktı: 0, 1, 2

// Tuzak 2: Hafıza sızıntısı
function potansiyelSizinti() {
  const buyukVeri = new Array(1000000).fill("veri");

  return function() {
    // buyukVeri hâlâ bellekte tutulur, kullanılmasa bile
    console.log("Merhaba");
  };
}

// Çözüm: İhtiyaç duyulmayan referansları temizleyin
function duzeltilmis() {
  let buyukVeri = new Array(1000000).fill("veri");
  const sonuc = isleVeriyi(buyukVeri);
  buyukVeri = null; // Referansı serbest bırak

  return function() {
    console.log(sonuc);
  };
}
Uyarı: Closure'lar dış kapsamdaki tüm değişkenlere referans tutar. Büyük veri yapılarını closure içinde gereksiz yere tutmak hafıza sızıntılarına yol açabilir. Kullanılmayan referansları null olarak ayarlayarak garbage collector'ın çalışmasını sağlayın.

5. Modül Pattern'i

Closure'ların en önemli kullanım alanlarından biri, ES6 modülleri öncesinde yaygın olarak kullanılan modül pattern'idir.

// IIFE ile modül pattern'i
const Dogrulayici = (function() {
  // Private değişkenler ve fonksiyonlar
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  const telefonRegex = /^(\+90|0)?[0-9]{10}$/;

  function temizle(deger) {
    return String(deger).trim();
  }

  // Public API
  return {
    emailDogrula(email) {
      return emailRegex.test(temizle(email));
    },
    telefonDogrula(telefon) {
      return telefonRegex.test(temizle(telefon).replace(/\s/g, ""));
    },
    zorunluAlanDogrula(deger) {
      return temizle(deger).length > 0;
    }
  };
})();

console.log(Dogrulayici.emailDogrula("test@ornek.com")); // true
console.log(Dogrulayici.telefonDogrula("+905551234567")); // true
// Dogrulayici.emailRegex; // undefined (private)
Bilgi: Modern JavaScript'te ES6 modülleri (import/export) tercih edilse de, modül pattern'ini anlamak closure'ların gücünü kavramak ve eski kod tabanlarıyla çalışmak için hâlâ çok değerlidir.

Sonuç

Closure ve scope kavramları JavaScript'in temel yapı taşlarıdır. Kapsam zincirinin nasıl çalıştığını, closure'ların değişken referanslarını nasıl koruduğunu ve bu mekanizmayı veri gizleme, memoization, fonksiyon fabrikaları ve modül pattern'i gibi ileri düzey tekniklerde nasıl kullanabileceğinizi bilmek, profesyonel JavaScript geliştirmenin olmazsa olmazıdır. Yaygın tuzaklardan kaçınarak closure'ları bilinçli şekilde kullanın.

Kaynaklar

İlgili Yazılar