Yazılarımız

OfisData

SCOPE VE CLOSURE KAVRAMINI ÖRNEKLERLE PEKİŞTİRMEK

JavaScript yazarken bazı hatalar “neden bu değişken burada görünmüyor?” ya da “neden değer eski kaldı?” diye düşündürür. Bu soruların arkasında çoğu zaman scope ve closure vardır. İkisi bir araya geldiğinde, kodun akışını daha net okumanıza ve yeniden kullanılabilir yapılar kurmanıza yardım eder.

Özellikle arayüz geliştirme, otomasyon script’leri, veri işleme ve entegrasyon işlerinde; birden çok fonksiyonun birbirini çağırdığı yapılarda kapsam yönetimi kritik hale gelir. Doğru anlaşılan scope ve closure, hata ayıklamayı hızlandırır, test yazmayı kolaylaştırır ve beklenmeyen yan etkileri azaltır.

Bu yazıda “scope ve closure” konusunu ezberden değil, günlük kodun içinde karşılaşılan örneklerle pekiştireceğiz. Hedefimiz, kavramları pratikte tanımak; hangi durumda hangi yaklaşımı seçmeniz gerektiğini netleştirmek.


Primary keyword ile scope ve closure temellerini kavramak

Scope, bir değişkene nereden erişebileceğinizi belirleyen “kapsam” kuralıdır. Closure ise bir fonksiyonun, tanımlandığı kapsamı “hatırlaması” ve o kapsamdaki değişkenlere sonradan da erişebilmesidir. Bu iki kavram birlikte, kodun görünürlüğünü ve durum yönetimini şekillendirir.

Birçok projede hatalar, scope’un yanlış varsayılmasından doğar: yanlış yerde tanımlanan değişken, beklenmeyen gölgeleme (shadowing), döngü içinde yanlış değer yakalama gibi. Closure ise doğru kullanıldığında “private state” gibi güçlü bir araçtır; yanlış kullanıldığında ise gereksiz bellek tutma gibi sorunlara yol açabilir.

Bir editörde kapsam zinciri ve iç içe fonksiyon yapısı, değişkenlerin nereden erişildiğini vurgulayan işaretlemeler

JavaScript scope türlerini örneklemek ve ayırt etmek

JavaScript’te en sık konuşulan kapsam türleri: global scope, function scope ve block scope’tur. Modern kodda let ve const ile block scope daha görünür hale gelir. “Hoisting” gibi davranışlar da scope algısını etkiler; bu yüzden kapsam türlerini somut örneklerle ayırmak önemlidir.

Function scope ile block scope farkını pekiştirmek

Function scope, değişkenin fonksiyon içinde erişilebilir olmasıdır. Block scope ise { } bloklarıyla sınırlı bir erişim alanı sağlar. Özellikle if, for ve while bloklarında bu fark, beklenmeyen bug’ları engeller.

var, let, const kullanımını sahada karşılaştırmak

var function scope’a bağlıdır ve hoisting davranışı nedeniyle bazen kafa karıştırır. let/const block scope sağlar ve “temporal dead zone” nedeniyle daha güvenli kullanım sunar. Aşağıdaki örnek, döngü içindeki değişken erişimini netleştirir:

// Döngü içinde değer yakalama farkı
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log('var i:', i), 10);
}
// Çıktı: var i: 3 (3 kez)

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log('let j:', j), 10);
}
// Çıktı: let j: 0, 1, 2

Burada önemli nokta şudur: var ile tek bir “i” değişkeni paylaşılır; timeout çalıştığında döngü bitmiş olur. let ile her iterasyonda ayrı bir “j” bağlamı oluşur. Bu davranış closure’ı anlamanız için harika bir köprü kurar.

Lexical environment mantığını içselleştirmek ve okumak

Closure’ı gerçekten kavramanın yolu, JavaScript’in “lexical environment” (sözdizimsel çevre) mantığını okumaktan geçer. Bir fonksiyon nerede tanımlandıysa, o noktadaki kapsam zincirini referans alır. Yani çağrıldığı yer değil, tanımlandığı yer belirleyicidir.

Tanımlandığı yerin etkisini örneklemek ve hata ayıklamak

Bu ayrım, modüler yapılarda sık görülür. Örneğin bir fonksiyonu farklı yerlerden çağırıyor olsanız bile, closure sayesinde eriştiği değerler değişmeyebilir. Bu, “neden bu değer güncellenmedi?” gibi soruların temelidir.

// Lexical çevre: fonksiyon nerede tanımlandıysa orayı hatırlar
function makeLogger(prefix) {
  return function log(message) {
    console.log(prefix + ': ' + message);
  };
}

const info = makeLogger('INFO');
const warn = makeLogger('WARN');

info('Sistem hazır');
warn('Gecikme tespit edildi');

Bu örnekte log fonksiyonu, prefix değerini closure ile “taşır”. Aynı fonksiyon şablonundan farklı logger’lar üretirsiniz. Buradaki yaklaşım, tekrar eden kodu azaltır ve davranışı netleştirir.

Closure ile private state yönetimini uygulamak ve ölçeklemek

Closure’ın en pratik kullanımı, bir fonksiyonun içindeki durumu dışarıya kontrollü şekilde açmaktır. Bu sayede “private state” elde edersiniz: dışarıdan doğrudan müdahale edilemeyen, fakat fonksiyonlar üzerinden yönetilebilen bir durum.

Sayaç örneğiyle encapsulation kurmayı göstermek

Aşağıdaki yapı, state’i korur ve sadece gerekli aksiyonları dışarı verir. Bu yaklaşım, küçük araç fonksiyonlarında ve UI bileşen mantıklarında oldukça etkilidir.

// Private state: count dışarıdan doğrudan erişilemez
function createCounter(initialValue = 0) {
  let count = initialValue;

  return {
    inc() { count++; return count; },
    dec() { count--; return count; },
    get() { return count; }
  };
}

const counter = createCounter(10);
counter.inc(); // 11
counter.get(); // 11

Bu örnek, closure’ın “state saklama” gücünü gösterir. Dikkat: Bu güç, doğru sınırlarla kullanıldığında faydalıdır. Gereksiz state saklamak, bakım maliyetini artırabilir.

Callback ve event listener senaryolarını güvenle yönetmek

Gerçek projelerde closure en çok callback’lerde görünür: asenkron işler, event listener’lar, zamanlayıcılar, promise zincirleri. Bu senaryolarda doğru kapsam yönetimi; yanlış değişken yakalamayı, eski referanslarla çalışmayı ve “neden bu fonksiyon hâlâ eski değeri kullanıyor?” sorusunu azaltır.

Event listener içinde closure kullanımını düzenlemek

Bir buton tıklamasında, o anki “kullanıcı seçimi” gibi bir değeri yakalamak isteyebilirsiniz. Değeri globalde tutmak yerine, listener’a taşımak daha güvenlidir. Yine de listener’ların kaldırılmaması durumunda bellek tutulabileceğini unutmayın.

  • Listener eklerken kaldırma stratejisini planlamak
  • Closure içine büyük objeler taşımamak
  • Gereksiz referansları null yaparak serbest bırakmak

Hoisting ve gölgeleme tuzaklarını fark etmek ve önlemek

Scope konusunu zorlaştıran iki yaygın tuzak vardır: hoisting ve gölgeleme (shadowing). Hoisting, bildirimlerin yukarı taşınması gibi görünen davranıştır. Shadowing ise iç scope’ta aynı isimli değişkenle dış scope’u “gölgelemenizdir”.

Shadowing etkisini okunabilirlikle dengelemek

Shadowing bazen bilinçli yapılabilir, fakat çoğu zaman okunabilirliği düşürür. Özellikle aynı isimli değişkenler farklı anlamlara geliyorsa, isimlendirmeyi iyileştirmek daha sağlıklı olur. Okunabilir kod için scope sınırlarını belirgin tutmak, ekip içinde aktarımı hızlandırır.

Performans ve bellek yönetimini closure ile iyileştirmek

Closure, tanımlandığı çevreyi tutabildiği için bazı durumlarda bellek kullanımını etkileyebilir. Bu, closure “kötüdür” demek değildir; sadece uzun yaşayan referanslar varsa dikkatli olmanız gerekir. Örneğin bir sayfada tek sefer kurulup hiç temizlenmeyen listener’lar, closure içindeki büyük veri yapılarını beklenenden uzun süre tutabilir.

Memory leak riskini azaltacak pratikleri listelemek

Aşağıdaki pratikler, özellikle yoğun etkileşimli arayüzlerde işinizi kolaylaştırır:

  1. Uzun yaşayan closure içinde büyük veri taşımaktan kaçınmak
  2. DOM referanslarını gerektiğinde serbest bırakmak
  3. Tekrarlı kurulum yapan fonksiyonları idempotent tasarlamak
  4. Temizleme (cleanup) fonksiyonlarıyla yaşam döngüsünü yönetmek

Öğrenmeyi hızlandıracak pratik planı uygulamak ve ölçmek

Kavramları okumak güzel, fakat pekiştirme kod yazarak olur. Her gün 20–30 dakikalık pratikle scope zincirini okumaya başlayabilir, closure’ı küçük araçlar üzerinden geliştirebilirsiniz. Bir hafta sonunda “neden böyle oldu?” sorularınızın büyük bölümü yerini “bunu böyle tasarlasam daha iyi” düşüncesine bırakır.

Mini egzersizlerle kendi örneklerinizi üretmek

Şu mini egzersizlerle hızlı ilerleyebilirsiniz: (1) for döngüsü + timeout varyasyonları yazmak, (2) createCounter benzeri 3 farklı state yöneticisi üretmek, (3) bir “once” fonksiyonu yazarak bir fonksiyonun yalnızca bir kez çalışmasını sağlamak, (4) bir cache mekanizması kurup closure ile saklamak. Her egzersizde, hangi değişkenin nerede yaşadığını not edin.

Bu konuyu ekip içinde standartlaştırmak, yeni başlayanların adaptasyonunu hızlandırır ve code review süreçlerinde ortak bir dil oluşturur. Daha sistemli ilerlemek isterseniz, pratik ağırlıklı içeriklerle JavaScript eğitimi sayfasına göz atabilirsiniz.

 Vimaj