JavaScript kapsam ve kaplamları anlamak

Bu makalenin orijinali (Explaining JavaScript scope and closures) Robert Nyman tarafından kendi blogunda yazılmıştır. Türkçe çevirisi için kendisinden izin alınmış olup, aşağıda okuyacağınız birinci tekil şahıs yine Robert Nyman’ın kendisidir;

Javascript ile uygulama geliştirirken bir çok insanın zorluk çektiği bir konuyu açıklamaya çalışmak istedim.

Arkaplan

Kapsam ve kaplamları anlatmaya çalışan bir sürü makale olmasına rağmen bir çoğunun net bir şekilde konuyu ele almadığını söyleyebilirim. Bunun yanında bir çok makalede de herkesin daha önce 15 başka yazılım dili ile uygulama geliştirdiğini varsayılıyor ki deneyimlerime göre JavaScript yazan bir çok kişi C ve Java yerine HTML ve CSS altyapısından geliyor.

Bu nedenle bu makale ile mütevazi hedefim herkesin kapsam ve kaplamların ne olduğunu kavraması, nasıl çalıştıklarını ve daha önemlisi bunlardan nasıl yararlanabileceklerini görmeleri. Okumaya başlamadan önce değişkenler ve fonksiyonların temellerini anlıyor olmalısınız.

Kapsam

Kapsam, değişkenlerin ve fonksiyonların nerede erişilebilir oldukları ve hangi bağlam içinde çalıştırıldıklarını refere eder. Basitçe, bir değişken ya da fonksiyon, genel ya da yerel kapsam içinde tanımlanabilir. Değişkenler sözde fonksiyon kapsamına sahiptirler ve fonksiyonlar da değişkenlerle aynı kapsama sahiptirler.

Genel Kapsam

Bir şey genel olduğunda bu demektir ki ona kodunuzda istediğiniz yerden erişebilirsiniz. Şu örneği ele alalım:

var maymun = "Goril";

function misafiriKarsila() {
  return alert("Merhaba değerli blog okuyucusu!");
}

Bu kod bir tarayıcıda çalıştığında, fonksiyon kapsamı pencere olacaktı, bu da ilgili tarayıcı penceresinde çalışan her şey için fonksiyonu erişilebilir kılacaktı.

Yerel Kapsam

Genel kapsamın tersine, yerel kapsam bir şeyin, örneğin bir fonksiyonun, kodun belli bir kısmında tanımlanmış olmasıdır. Örneğin;

function terbiyesizKonus() {
  var konusma = "Oh, seni gidi VB sever seni";
  return alert(konusma);
}
alert(konusma); // Hata fırlatır

Yukardaki koda bakarsanız konusma değişkeni sadece terbiyesizKonus fonksiyonu içinde erişilebilir durumdadır. Fonksiyonun dışında tanımlı değildir. Buraya dikkat: eğer değişkeni tanımlarken öncesinde var kullanmazsanız, değişken otomatik olarak bir genel değişken olur.

Bu aynı zamanda şu manaya da gelir, iç içe fonksiyonlar tanımladığınızda içerdeki fonksiyon, içinde bulunduğu fonksiyonun değişkenlerine ve fonksiyonlarına erişebilir:

function adSakla(ad) {
  function buyukHarfeCevir() {
    return ad.toUpperCase();
  }
  var buyutulmus = buyukHarfeCevir();
  return buyutulmus;
}
alert(adSakla("Robert")); // "ROBERT" döndürür

Gördüğünüz gibi, iç fonksiyon buyukHarfeCevir işlem yapmak için herhangi bir parametreye ihtiyaç duymadı fakat dış fonksiyon adSakla’nın parametresi olan ad parametresine tam erişim sağladı. Daha net anlaşılması için bir örnek daha ele alalım:

function kardesler() {
  var kardesler = ["Mehmet", "Mahmut", "Ayşe"];
  function kardesSayisi() {
    var kardeslerUzunlugu = kardesler.length;
    return kardeslerUzunlugu;
  }
  function kardesIsimleriniBirlestir() {
    return "Benim " + kardesSayisi() + " kardeşim var:\n\n" + kardesler.join("\n");
  }
  return kardesIsimleriniBirlestir();
}
alert(kardesler()); // "Benim 3 kardeşim var: Mehmet Mahmut Ayşe" döndürür

Az önce gördüğünüz gibi, her iki iç fonksiyon da kendilerini kapsayan fonksiyona ait dizi değişkenine erişebildiler ve her iç fonksiyon kendisiyle aynı seviyedeki diğer iç fonksiyonlara (bu durumda kardesIsimleriniBirlestir fonksiyonu kardesSayisi fonksiyonuna erişti) erişebildi. Ne varki kardeslerUzunlugu değişkeni sadece ilgili fonksiyonda erişilebilir durumda, başka bir deyişle o kapsamda erişilebilir.

Kaplamlar

Hazır kapsam konusunu daha iyi kavramışken şimdi karışıma bir de kaplamları ekleyelim. Kaplamlar, genellikle, belirli bir bağlamda değişkenlerle çaışabilen ifadelerdir ya da işi daha da kolaylaştırmak adına, dış fonksiyonların yerel değişkenleri ile işlem yapan iç fonksiyonlar kaplamları oluşturur. Örneğin:

function topla(x) {
  return function(y) {
    return x + y;
  };
}
var topla5 = topla(5);
var sayi8 = topla5(3);
alert(sayi8); // 8 döndürür

Dur bir dakika! Az önce ne oldu? Şunu parçalarına ayıralım:

  1. topla fonksiyonu çağırıldığında geriye bir fonksiyon döndürüyor.
  2. Bu fonksiyon ilgili bağlamı kapatıyor ve x parametresinin o anki değerini hafızasında tutuyor.(Yukardaki örnekte 5 değeri)
  3. topla5 değişkenine değeri atanırken, dönen fonksiyon x değerinin 5 olduğunu hep biliyor.
  4. topla5 değişkeni parametre olarak aldığı değere 5 ekleyen bir fonksiyonu refere eder hale geliyor.
  5. topla5 fonksiyonu 3 parametresi ile çağrıldığında, 5 değerine 3 ekliyor ve 8 değerini dönüyor.

Yani JavaScript dünyasında topla5 fonksiyonu aslında şuna benzer:

function topla5 (y) {
  return 5 + y;
}

Rezil döngü problemi

Kaç defa, bir döngü yaratıp, i değerini bir elemana atamaya çalıştığınızda, gördünüz ki sadece i’nin en son değeri geri dönüyor?

Yanlış referans

Şimdi, 5 adet a elemanı yaratıp, i değişkeni ile metinlerini oluşturan ve tıklama olayında da i değerini mesaj kutusu (alert) ile ekrana basan, şu yanlış koda bir bakalım. Oluşan a elemanları dokümanın (document) gövdesine (body) eklenecekler:

function baglantiEkle() {
  for (var i=0, baglanti; i < 5; i++) {
    baglanti = document.createElement("a");
    baglanti.innerHTML = "Bağlantı " + i;
    baglanti.onclick = function () {
      alert(i);
    };
    document.body.appendChild(baglanti);
  }
}
window.onload = baglantiEkle;

Her eleman "Bağlantı 0", "Bağlantı 1" vb doğru metinlere sahip olacaklar. Fakat hangi bağlantıya tıklarsanız tıklayın, mesaj kutusunda hep "5" yazacaktır. Peki ama neden? Döngünün her yinelenmesinde i değeri bir artıyor ve tıklama olayı sadece elemana bağlanıyor ama henüz çalıştırılmıyor.

Böylece döngü i değeri 5 olana kadar devam ediyor, ki bu da baglantiEkle fonksiyonu çalışmayı bitirmeden önce i'nin aldığı son değer. Sonrasında da her tıklama olayı gerçekleştiğinde, son i değeri kullanılıyor.

Doğru referans

Bunun yerine yapmanız gereken bir kaplam yaratmak, böylece tıklama olayını her bağladığınızda o anki doğru i değeri ile olay çalışır. Şöyle ki:

function baglantiEkle() {
  for (var i=0, baglanti; i < 5; i++) {
    baglanti = document.createElement("a");
    baglanti.innerHTML = "Bağlantı " + i;
    baglanti.onclick = function (numara) {
      return function () {
        alert(numara);
      };
    }(i);
    document.body.appendChild(baglanti);
  }
}
window.onload = baglantiEkle;

Bu kod ile her a elemanına tıkladığınızda "Bağlantı 0", "Bağlantı 1" gibi ilk kod parçasında olmasını beklediğiniz değerler karşınıza çıkacaktır. Burada çözümü sağlayan, tıklama olayına atanan iç fonksiyon numara değişkenini -bu örnekte parametre olarak geçilen i değişkeninin o andaki değeri- refere eden bir kaplam oluşturmasıdır.

Bu fonksiyon geçerli değer ile kapanıyor ve bu sayede tıklama olayı çağırıldığında olması gereken değeri dönebiliyor.

Kendi kendini çalıştıran fonksiyonlar

Kendi kendini çalıştıran fonksiyonlar anında çalışan ve kendi kaplamlarını oluşturan fonksiyonlardır. Şuna bir göz atın:

(function () {
  var kopek = "Alpan Çobanı";
  alert(kopek);
})();
alert(kopek); // undefined (tanımlanmamış) geri döner

Tamam, demek ki kopek değişkeni sadece o bağlamda uygun durumda. Büyük iş, gizli köpekler... Ama, sevgili arkadaşlarım, işte olayın ilginçleştiği nokta da bu! Biraz önceki döngüde sorunumuzu çözen şey bu. Yahoo da bunu Yahoo JavaScript Modül Deseni için kullanıyor.

Yahoo JavaScript Modül Deseni

Desenin özü şu; Kendi kendini çağıran fonksiyonlar kullanarak bir kaplam yaratıyoruz, bu sayede özel ve genel değişkenler ve metotlar oluşturabiliyoruz. Basit bir örnek:

var kisi = function () {
  // Özel
  var isim = "Robert";
  return {
    ismiGetir : function () {
      return isim;
    },
    ismiBelirle : function (yeniIsim) {
      isim = yeniIsim;
    }
  };
}();
alert(kisi.isim); // undefined
alert(kisi.ismiGetir()); // "Robert"
kisi.ismiBelirle("Robert Nyman");
alert(kisi.ismiGetir()); // "Robert Nyman"

Bunun güzelliği, artık nelerin genel olarak görüneceğini(ve değiştirilebileceğini), nelerin ise özel olacağını dolayısıyla da kimsenin erişememesine ve değiştirememesine siz karar verebileceksiniz. Yukardaki değişkenin ismi fonksiyonun bağlamı dışında gizlidir, fakat ismiGetir ve ismiBelirle fonksiyonları aynı kaplamda yaratıldıkları için ilgili değişkenin referansına sahiptirler ve kendisine ulaşabilirler.

Sonuç

Naçizane umudum, bu yazıyı okuduktan sonra, ister acemi, ister uzman bir programcı olun, JavaScript'deki kapsam ve kaplamlar ile ilgili daha net bir görüş açısı kazanmanızdır. Sorular ve geri beslemelere her zaman açığım ve konuyla ilgili gerçekten önemli her türlü bilgi için makaleyi hemen güncellerim.

Mutlu kodlamalar!

“JavaScript kapsam ve kaplamları anlamak” üzerine 13 düşünce

  1. Ben de makaleyi kimsenin okumadığını düşünmeye başlamıştım ki sizin yorumunuz geldi. :) Güzel oldu.

  2. Sağolun, varolun. Spam olmayan teşekkür yorumları gelmeyeli baya olmuştu. İyi geldi. :)

  3. Hocam ellerinize kollarınaza sağlık , siz böyle cevrii yapıp paylaşırsınızda okumamak olmaz mı :)

  4. Yazı için çok teşekkürler. Sayenizde bilmediğim çok şey öğrendim :))

  5. Geri bildirim: Anonim

Yorumlar kapalı.