JAVASCRIPT SCOPE CLOSURE ÖRNEKLEMEK

Vimaj9 dk okuma
Sarı JavaScript logosu yanında iç içe geçmiş outer ve inner scope kutuları ile zincir sembolü scope chain hiyerarşisi

Scope basitçe "değişken hangi alanda görünür" sorusunu cevaplar. Closure ise "bir fonksiyon dışındaki değişkenlere nasıl erişir" sorusunu. İkisi birlikte JavaScript'in en güçlü ama en kafa karıştırıcı özelliklerinden; bu iki kavramı anlamadan modern JavaScript yazılamaz.

1. Scope nedir?

Bir değişkenin görünür olduğu kod alanı. Kavramların kesin tanımları için web teknolojileri dokümantasyonu ayrıntılı örnekler içerir. JavaScript'te üç scope türü var:

1. Global scope

Tüm dosyada görünür; en üst seviye.

const isim = 'Ahmet'; // global scope

function selamla() {
  console.log(isim); // erişebilir
}

2. Function scope

Fonksiyon içinde tanımlanan değişkenler sadece o fonksiyon içinde görünür.

function hesapla() {
  const sonuc = 42; // function scope
  console.log(sonuc); // OK
}

console.log(sonuc); // HATA: sonuc tanımsız

3. Block scope

let ve const ile tanımlanan değişkenler süslü parantez { } içinde görünür.

if (true) {
  let x = 10; // block scope
}

console.log(x); // HATA

// var farklı; function scoped
if (true) {
  var y = 10; // function veya global scope
}

console.log(y); // 10 (erişilebilir)

2. var, let, const farkı

Özellikvarletconst
ScopeFunctionBlockBlock
HoistingYes (undefined)Yes (TDZ)Yes (TDZ)
Redeclareİzin varİzin yokİzin yok
Reassignİzin varİzin varİzin yok

Modern pratik: const varsayılan; reassign gerekiyorsa let; var kullanma.

3. Scope chain

İç içe fonksiyonlarda değişken arama: en içten dışa doğru.

const a = 1; // global

function disFonksiyon() {
  const b = 2; // disFonksiyon scope

  function icFonksiyon() {
    const c = 3; // icFonksiyon scope
    console.log(a, b, c); // 1, 2, 3
    // a global'dan, b dış fonksiyondan, c kendi scope'tan
  }

  icFonksiyon();
}

icFonksiyon a'yı doğrudan bulamaz; scope chain ile yukarı çıkıp önce disFonksiyon'da bakar, sonra global'a bakar. Bulduğu ilk eşleşmeyi alır.

4. Closure nedir?

Bir fonksiyonun, kendisi tanımlandığı scope'taki değişkenlere erişebilmesi. Fonksiyon dışarı çıkarılsa bile o değişkenlere erişim devam eder.

function sayacOlustur() {
  let sayac = 0;

  return function() {
    sayac++;
    return sayac;
  };
}

const arttir = sayacOlustur();
console.log(arttir()); // 1
console.log(arttir()); // 2
console.log(arttir()); // 3

sayacOlustur fonksiyonu bittikten sonra bile dönen fonksiyon "sayac" değişkenine erişebiliyor. Bu closure'dur; dış fonksiyonun scope'unu "kapatıp" içeriye getiriyor.

VS Code dark theme editörde sayacOlustur closure fonksiyonu kodu syntax highlight ile dış değişken sayac kapatma örneği

5. Closure kullanım örnekleri

1. Private değişkenler

JavaScript'te native private değişken yoktu (modern class # syntax'ı hariç); closure ile simüle edilir.

function bankaHesabi(baslangic) {
  let bakiye = baslangic; // dışarıdan görünmez

  return {
    yatir: (miktar) => { bakiye += miktar; },
    cek: (miktar) => {
      if (miktar > bakiye) return 'Yetersiz';
      bakiye -= miktar;
      return bakiye;
    },
    sorgu: () => bakiye
  };
}

const hesap = bankaHesabi(1000);
hesap.yatir(500);
console.log(hesap.sorgu()); // 1500
console.log(hesap.bakiye); // undefined - dışarıdan erişim yok

2. Currying

Bir fonksiyonu parça parça çağırma; her aşamada bir argüman alıp yeni fonksiyon döner.

function carpan(x) {
  return function(y) {
    return x * y;
  };
}

const ikiyleCarp = carpan(2);
const beslyeCarp = carpan(5);

console.log(ikiyleCarp(10)); // 20
console.log(beslyeCarp(10)); // 50

3. Event handler'larda state

function butonHazirla() {
  let tiklamaSayisi = 0;

  document.querySelector('button').addEventListener('click', () => {
    tiklamaSayisi++;
    console.log(`Tıklama: ${tiklamaSayisi}`);
  });
}

butonHazirla();
// Buton tıklandığında tiklamaSayisi closure ile korunur

4. setTimeout closure

function gecikmeliMesaj(mesaj, ms) {
  setTimeout(() => {
    console.log(mesaj); // closure ile mesaj'a erişir
  }, ms);
}

gecikmeliMesaj('Merhaba', 1000);
// Fonksiyon biter ama setTimeout callback'i mesaj'ı görür

6. Closure tuzakları

1. for döngüsünde var ile closure sorunu

// Yanlış: var function-scoped
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Çıktı: 3, 3, 3 (beklenen 0, 1, 2 değil)
// Sebep: tüm callback'ler aynı i'yi paylaşır

// Doğru: let block-scoped
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// Çıktı: 0, 1, 2 (her iterasyonda yeni i)

2. Memory leak riski

Closure dış scope'a referans tutar; o scope'taki tüm değişkenler bellekte kalır. Büyük objeleri yanlış closure'a sokmak memory leak yaratır.

function bigDataIslem() {
  const buyukVeri = new Array(1000000).fill('x');

  return function() {
    console.log('İşlem yapıldı');
    // buyukVeri kullanılmıyor ama closure ile bellekte tutulur
  };
}

const handler = bigDataIslem();
// buyukVeri hâlâ bellekte; garbage collect olmaz

Çözüm: ihtiyaç olmayan değişkenleri closure dışında tut.

7. Hoisting

JavaScript değişken ve fonksiyon tanımlamalarını scope başına "kaldırır". var ile tanımlananlar undefined ile; let/const ise "temporal dead zone" (TDZ) ile.

console.log(a); // undefined (var hoisted)
var a = 5;

console.log(b); // HATA: TDZ
let b = 10;

selamla(); // OK (function declaration hoisted)
function selamla() {
  console.log('Merhaba');
}

selamla2(); // HATA: function expression hoisted ama undefined
const selamla2 = function() {
  console.log('Merhaba');
};

8. Lexical vs dynamic scope

JavaScript lexical scope kullanır: bir değişkenin nereden erişilebileceği kod yazılırken belirlenir; çalışma anında değil.

const isim = 'Global';

function selamla() {
  console.log(isim); // 'Global' (lexical)
}

function farkliScope() {
  const isim = 'Lokal';
  selamla(); // hâlâ 'Global' yazar
  // Çünkü selamla tanımlandığı yerdeki isim'i görür
}

farkliScope();

9. this ve scope ilişkisi

this dinamik; çağrıldığı bağlama göre değişir. Arrow function'lar this'i kapatır (closure benzeri).

class Sayac {
  constructor() {
    this.deger = 0;
  }

  baslat() {
    // setInterval callback'i farklı this'e sahip
    setInterval(function() {
      this.deger++; // HATA: this undefined
    }, 1000);

    // Arrow function this'i kapatır
    setInterval(() => {
      this.deger++; // OK: outer this kullanılır
    }, 1000);
  }
}
Lexical scope chain diyagramında global block dış fonksiyon iç fonksiyon iç içe kutular ve yukarı yönlü değişken arama okları

10. Tipik Hatalar

  1. var kullanmak. Modern JavaScript'te let/const tercih edilir; var bug riski yüksek
  2. for içinde var ile closure. Klasik soru; let kullanmak çözer
  3. Global scope kirletme. Modül yapısı kullanmadan global değişken eklemek
  4. Memory leak. Closure ile büyük objeleri tutmak
  5. this'in scope ile karıştırılması. this dinamik; scope statik

11. Konu Üzerinde Derinleşmek

Scope ve closure modern JavaScript'in temel taşları. Konuya hâkim olmak için JavaScript eğitimi programları bu kavramları derinlemesine işler.

12. Kısa Özet

JavaScript'te scope değişken görünürlüğünü; closure fonksiyon ile değişkenlerin "kapatılmasını" yönetir. Block scope (let/const), function scope (var), global scope üç ana türü. Closure private değişkenler, currying, event handler state'i gibi pratik kullanımlar için temeldir. var yerine let/const, for döngüsünde let, arrow function ile this kapatma modern pratiklerdir. Closure memory leak'e dikkat; gereksiz büyük objeleri kapsam dışında tutun.

Sıkça Sorulan Sorular

Closure'ı pratik olarak nerede kullanırım?

En yaygın kullanım: private değişkenler (Module pattern), event handler'larda state, setTimeout/setInterval içinde değişken erişimi, currying ve higher-order functions, React useState gibi hooks'ların altyapısı. Modern JavaScript'in çoğu özelliği altta closure kullanır; bilerek yazmasanız da kütüphaneler closure ile çalışır.

var yerine neden let kullanmalıyım?

Üç ana sebep. Birincisi: block scope; daha güvenli ve öngörülebilir. var function scope'lu olduğu için beklenmedik davranışlar yaratır. İkincisi: redeclare engellenir; aynı isimle iki değişken tanımlamak hata verir, bug'ları önler. Üçüncüsü: TDZ (temporal dead zone) hoisting davranışını daha sıkı yapar. Modern JavaScript'te var artık tercih edilmez.

Closure memory leak'e neden olur mu?

Olabilir. Closure dış scope'taki tüm değişkenlere referans tutar; o değişkenler kullanılmasa bile bellekten silinmez. Özellikle DOM element'lerine veya büyük data structure'lara referans tutan closure'lar tehlikeli. Çözüm: gerçekten ihtiyaç olmayan değişkenleri closure dışında tutun; gerekirse null ataması ile manuel temizleyin.

Block scope ne işe yarar?

Değişkenleri sadece gerekli alanda tutmaya yarar. if, for, while gibi blokların içinde tanımlanan let/const değişkenler dışarıdan erişilemez. Bu kapsam küçültme bug'ları azaltır; global namespace temiz kalır. Function scope'a göre çok daha kısıtlayıcı ama güvenli.

JavaScript dinamik scope kullanır mı?

Hayır, lexical scope kullanır. Bir değişkenin nereden erişilebileceği kod yazılırken belirlenir; çalışma anında değil. Bash ve bazı eski diller dynamic scope kullanır (çağrılan yere göre değişken bakar). JavaScript'te lexical scope sayesinde kodu okurken davranışı tahmin etmek mümkün; bu büyük bir avantaj.

this neden scope'tan farklı?

this dinamik bir referans; çağrı bağlamına göre değişir. Aynı fonksiyon farklı şekillerde çağrıldığında farklı this değeri alır. Scope ise statik; fonksiyon tanımlandığı yer belirler. Arrow function'lar this'i 'lexical' yapar; tanımlandığı yerin this'ini alır. Bu, callback'lerde sık yaşanan 'this undefined' sorununu çözer.

Hoisting tam olarak ne yapar?

JavaScript yorumlayıcısı kod çalıştırmadan önce tüm tanımlamaları (var, function declaration) scope başına 'kaldırır'. var ile tanımlanan değişkenler undefined olarak hazır beklerken; let/const TDZ'de (henüz initialize edilmemiş) durur. Function declaration'lar tamamen hoisted; tanımlanmadan önce çağrılabilirler. Function expression (const fn = function(){}) sadece değişken hoist olur, fonksiyon değil.

 Vimaj