Discussion

Call Stack ve Execution Context Rehberi

Started by Nikolem · 10 Jul 2026 20:16 · 10 Views · 0 Replies
Thread Starter #0
Call Stack ve Execution Context Rehberi

Yazılım geliştirme serüvenimizde, özellikle JavaScript gibi dinamik dillerle çalışırken, kodumuzun sahne arkasında nasıl işlediğini tam olarak anlamak, sadece "çalışıyor" demekten çok daha fazlasını ifade eder. Belki scope, hoisting, this gibi terimleri sıkça kullanıyoruz, ancak bu mekanizmaların temelinde yatan Execution Context (Yürütme Bağlamı) ve Call Stack (Çağrı Yığını) yapılarını ne kadar derinden kavrayabiliyoruz? İşte tam da bu noktada, bir adım geri çekilip bu iki kritik kavramı enine boyuna incelemenin zamanı geldiğini düşünüyorum. Gelin, kodumuzun nasıl bir ortamda çalıştığını, fonksiyon çağrılarının nasıl yönetildiğini ve bu bilgilerin bize hata ayıklamadan performansa kadar ne gibi avantajlar sağladığını detaylıca keşfedelim.

Execution Context (Yürütme Bağlamı): Kodun Sahnesi

Bir Execution Context'i, kodunuzun çalıştırıldığı soyut bir "ortam" olarak düşünebilirsiniz. Her JavaScript kodu parçası, kendine ait özel bir yürütme bağlamı içinde çalışır. Bu bağlam, kodun ihtiyaç duyduğu her şeyi barındırır: değişkenleri, fonksiyonları, this değerini ve hatta kodun dış dünyayla nasıl iletişim kuracağını belirleyen referansları... Tıpkı bir tiyatro sahnesi gibi, her oyunun (yani her kod parçasının) kendi sahnesi, dekoru ve oyuncuları vardır.

JavaScript'te temel olarak üç tür Execution Context bulunur:

  • Global Execution Context (GEC): Tarayıcıda bir web sayfası açtığınızda veya bir Node.js uygulamasını başlattığınızda ilk oluşan bağlam budur. Tüm genel kapsamdaki (global scope) kodlar burada çalışır. Tarayıcı ortamında window objesi, Node.js'te ise global objesi bu bağlamın bir parçasıdır. this anahtar kelimesi, GEC içinde genellikle window veya global objesine işaret eder. Yani, her şeyin başladığı yer burası diyebiliriz.
  • Function Execution Context (FEC): Bir fonksiyon çağrıldığında, o fonksiyona özel yeni bir yürütme bağlamı oluşturulur. Bu bağlam, fonksiyonun argümanlarını, yerel değişkenlerini ve içinde tanımlanan diğer fonksiyonları içerir. Her fonksiyon çağrısı, yepyeni bir FEC demektir. Fonksiyon bittiğinde, bu bağlam da genellikle yok olur.
  • Eval Execution Context: eval() fonksiyonu ile çalıştırılan kodlar için de ayrı bir yürütme bağlamı oluşturulur. Ancak performans ve güvenlik nedenleriyle eval() kullanımı pek önerilmez, bu yüzden genellikle GEC ve FEC üzerinde dururuz.

Peki, bir Execution Context'in içinde neler var? Bu bağlam, üç ana bileşenden oluşur:

  • Lexical Environment (Ortam Sözlüğü): Bu, bağlamın en kritik parçalarından biri. İki alt bileşeni var:
    • Environment Record (Değişken Ortamı): Bu kısım, o bağlam içinde tanımlanan tüm değişkenleri ve fonksiyon bildirimlerini tutar. var ile tanımlanan değişkenlerin ve fonksiyon bildirimlerinin hoisting (yukarı çekilme) mekanizması burada gerçekleşir. Yani kod çalışmaya başlamadan önce bu bildirimler belleğe alınır. let ve const ile tanımlanan değişkenler de burada tutulur ancak hoisting davranışları var'dan farklıdır; Temporal Dead Zone (TDZ) denen bir durum söz konusudur.
    • Outer Environment Reference (Dış Ortam Referansı): Her bir Lexical Environment, kendisini oluşturan dış Lexical Environment'a bir referans tutar. İşte bu referanslar zinciri, "scope chain" (kapsam zinciri) dediğimiz yapıyı oluşturur. Bir değişken arandığında, önce mevcut Lexical Environment'ın kendi Environment Record'una bakılır, bulunamazsa Outer Environment Reference takip edilerek bir üst kapsama çıkılır ve bu böylece Global Execution Context'e kadar devam eder. Bu zincir sayesinde, bir fonksiyon kendi tanımlandığı kapsamdaki değişkenlere her zaman erişebilir, hatta çağrıldığı yer farklı olsa bile... Bu, Closure'ların temelini oluşturur.
  • This Binding (Bu Bağlamı): this anahtar kelimesinin değeri, Execution Context'in türüne ve fonksiyonun nasıl çağrıldığına bağlı olarak belirlenir. Bu, JavaScript'in en çok kafa karıştıran yönlerinden biri olabilir. Örneğin, global bağlamda this genellikle window veya global objesidir. Bir metot olarak çağrılan bir fonksiyonda this, o metodun ait olduğu objeyi gösterir. Bağımsız bir fonksiyon çağrısında (strict mode'da değilse) yine window veya global'a döner. Arrow function'lar ise kendi this bağlamlarını oluşturmazlar, tanımlandıkları Lexical Environment'ın this değerini alırlar. Bu dinamik yapı, call, apply, bind gibi metotlarla manuel olarak da değiştirilebilir.

Bu detaylar, kodunuzdaki değişkenlerin neden bazen beklendiği gibi davranmadığını veya this değerinin neden farklı yerlerde farklı sonuçlar verdiğini anlamak için hayati önem taşır.

Call Stack (Çağrı Yığını): İşlerin Sırası

Execution Context'ler, kodumuzun çalıştırıldığı ortamı tanımlarken, Call Stack ise bu ortamların hangi sırada çalıştığını yöneten bir veri yapısıdır. Adından da anlaşılacağı gibi, bir "yığın" (stack) yapısındadır. Bu, Last In, First Out (LIFO - Son Giren İlk Çıkar) prensibiyle çalışır. Tıpkı bir tabak yığını gibi düşünün; en son koyduğunuz tabağı ilk alırsınız.

JavaScript motoru, kodumuzu çalıştırırken aktif olan tüm Execution Context'leri Call Stack üzerinde tutar.

Peki, nasıl işler bu mekanizma? Adım adım bakalım:

  • Başlangıç: JavaScript kodu çalışmaya başladığında, ilk olarak Global Execution Context (GEC) oluşturulur ve Call Stack'in en altına itilir (push edilir). Artık GEC aktif bağlamdır.
  • Fonksiyon Çağrısı: Kod GEC içinde ilerlerken bir fonksiyon çağrısıyla karşılaştığında, o fonksiyon için yeni bir Function Execution Context (FEC) oluşturulur. Bu FEC, Call Stack'in en üstüne itilir ve artık bu yeni FEC aktif bağlam olur.
  • İç İçe Çağrılar: Eğer aktif olan FEC içinde başka bir fonksiyon çağrılırsa, onun için de yeni bir FEC oluşturulur ve yine Call Stack'in en üstüne itilir. Bu süreç, iç içe fonksiyon çağrıları oldukça devam eder.
  • Fonksiyon Dönüşü: Bir fonksiyon çalışmasını bitirdiğinde (yani return ifadesine ulaştığında veya fonksiyonun sonuna geldiğinde), o fonksiyona ait FEC Call Stack'ten çıkarılır (pop edilir). Artık bir altındaki FEC aktif bağlam olur ve kod oradan çalışmaya devam eder.
  • Bitiş: En sonunda, tüm fonksiyonlar tamamlandığında ve GEC de işini bitirdiğinde, o da Call Stack'ten çıkarılır. Call Stack boşaldığında, JavaScript motoru "boşta" kalır ve yeni olayları (event'leri) beklemeye başlar.

Bu mekanizma, JavaScript'in tek iş parçacıklı (single-threaded) yapısının temelini oluşturur. Yani, Call Stack'te aynı anda sadece bir Execution Context aktif olabilir ve kod sadece o bağlam içinde çalışır.

Bir Örnekle Çağrı Yığını

Şu basit JavaScript kodunu ele alalım:

CODE
12345678910111213141516
function ucuncu() {
    console.log("Üçüncü fonksiyon çalıştı.");
}

function ikinci() {
    ucuncu(); // ucuncu() fonksiyonunu çağırıyoruz
    console.log("İkinci fonksiyon çalıştı.");
}

function birinci() {
    ikinci(); // ikinci() fonksiyonunu çağırıyoruz
    console.log("Birinci fonksiyon çalıştı.");
}

birinci(); // birinci() fonksiyonunu çağırıyoruz


Bu kod çalıştığında Call Stack'te neler olur, adım adım görselleştirelim:

[LIST]
  • JavaScript motoru kodu okumaya başlar. Global Execution Context (GEC) oluşturulur ve yığına itilir.
    CODE
    12
        [GEC]
        

  • birinci() fonksiyonu çağrılır. Yeni bir Function Execution Context (FEC) birinci oluşturulur ve yığına itilir. birinci fonksiyonu aktif hale gelir.
  • You must be logged in to reply.

    0 quotes selected