Tartışma

Hoisting Nedir? JavaScript Bellek Yönetimi

Başlatan NeonSpectre · 10 Tem 2026 19:56 · 20 Görüntülenme · 0 Yanıtlar
Konuyu Açan #0
Hoisting Nedir? JavaScript Bellek Yönetimi

JavaScript'in doğasını anlamak, özellikle de dilin bazı "tuhaf" sayılabilecek davranışlarını çözmek, hem daha temiz kod yazmamızı sağlıyor hem de beklenmedik hataların önüne geçiyor. İşte bu noktada karşımıza çıkan en temel kavramlardan biri "hoisting". Çoğu zaman yeni başlayanların kafasını karıştıran, hatta deneyimli geliştiricilerin bile bazen gözden kaçırdığı bir detay olabiliyor. Ama aslında işin özü, JavaScript motorunun kodu nasıl işlediğiyle ilgili, yani bir nevi sahne arkası operasyonları...

Hoisting'e İlk Bakış: Bir Yanılgı mı, Gerçek mi?

Şimdi, "hoisting" kelimesi akla hemen bir şeylerin "yukarı kaldırılması"nı getiriyor, değil mi? İşte tam da bu noktada yaygın bir yanlış anlama başlıyor. Aslında kodumuz fiziksel olarak yukarı taşınmıyor. JavaScript, kodu çalıştırmadan önce iki ana aşamadan geçiyor: ilk olarak kodun sözdizimsel analizi (parsing) yapılıyor, ardından da yürütme (execution) aşamasına geçiliyor. Hoisting dediğimiz olay, aslında bu ilk aşamada, yani derleme (compilation) diyebileceğimiz süreçte gerçekleşiyor. Motor, kodun içinde tanımlanmış tüm değişkenleri ve fonksiyon bildirimlerini kendi hafızasına alıyor, yani bir nevi "tanımlandı" olarak işaretliyor.

Bu, özellikle var anahtar kelimesiyle tanımlanan değişkenler ve function declaration ile yapılan fonksiyonlar için geçerli. Yani kodunuzun herhangi bir yerinde bir var değişkeni tanımladığınızda veya bir fonksiyonu klasik yolla bildirdiğinizde, JavaScript motoru daha o satıra gelmeden önce o değişkenin veya fonksiyonun var olduğunu biliyor. Bu durum, bazen tanımsız (undefined) değerlerle karşılaşmamıza neden olurken, bazen de fonksiyonları tanımlandıkları yerden önce çağırabilmemizi sağlıyor. Birazdan örneklerle daha net göreceğiz.

Değişken Hoisting'i: var ile Gelen Karmaşa

var ile tanımlanmış değişkenlerin hoisting davranışı, JavaScript'in en çok eleştirilen yanlarından biri belki de. Çünkü var ile tanımlanan bir değişken, kapsamının (scope) en üstüne taşınır ve varsayılan olarak undefined değeriyle başlatılır. Yani siz daha değişkene bir değer atamadan önce bile ona erişebilirsiniz, ancak alacağınız değer undefined olacaktır. Bu durum, özellikle büyük kod tabanlarında beklenmedik hatalara yol açabiliyor, değil mi?

Şöyle bir örnekle açıklayalım:

CODE
1234
console.log(benimDegiskenim); // Çıktı: undefined
var benimDegiskenim = "Merhaba Dünya";
console.log(benimDegiskenim); // Çıktı: Merhaba Dünya


Bu kod aslında JavaScript motoru tarafından şu şekilde yorumlanıyor:

CODE
12345
var benimDegiskenim; // Deklarasyon (bildirim) hoisting edildi ve undefined ile başlatıldı
console.log(benimDegiskenim); // Bu noktada benimDegiskenim undefined
benimDegiskenim = "Merhaba Dünya"; // Atama (initialization) orijinal yerinde kaldı
console.log(benimDegiskenim);


Gördüğünüz gibi, var ile bir değişkeni bildirmek (declaration) ve ona bir değer atamak (initialization) iki ayrı olay. Hoisting sadece bildirim kısmını etkiliyor, atama işlemi kodun yazıldığı yerde kalıyor. Bu da, değişkenin tanımlandığı bloğun dışına sızabilen function scope yapısıyla birleşince, bazen ciddi bellek yönetimi sorunlarına ve hata ayıklaması zor bug'lara davetiye çıkarabiliyor.

let ve const ile Hoisting: Temporal Dead Zone (TDZ)

ES6 ile hayatımıza giren let ve const, var'ın bu kafa karıştırıcı davranışına bir çözüm getirdi. Her ikisi de block scope'a sahip olmalarıyla dikkat çekiyor ve hoisting mekanizmalarında var'dan farklı davranıyorlar. let ve const ile tanımlanan değişkenler de aslında hoisting ediliyor, ancak var gibi undefined ile başlatılmıyorlar. Bunun yerine, kapsamları başladığı andan itibaren değer ataması yapılana kadar "Temporal Dead Zone" (TDZ) adı verilen bir durumda kalıyorlar.

TDZ, bir değişkenin kapsamının başladığı ancak henüz başlatılmadığı (yani bir değer atanmadığı) zaman dilimini ifade ediyor. Bu süre zarfında değişkene erişmeye çalışırsanız, ReferenceError hatası alırsınız. Bu, undefined almaktan çok daha iyi, çünkü hatayı anında fark etmenizi ve kodunuzu düzeltmenizi sağlıyor. Yani kodunuzun daha öngörülebilir olmasını sağlıyor, bu da geliştirme sürecinde büyük bir avantaj...

Şu örneğe bir göz atalım:

CODE
12345678
console.log(benimLetDegiskenim); // Hata: ReferenceError: Cannot access 'benimLetDegiskenim' before initialization
let benimLetDegiskenim = "Merhaba Let";
console.log(benimLetDegiskenim);

console.log(benimConstDegiskenim); // Hata: ReferenceError: Cannot access 'benimConstDegiskenim' before initialization
const benimConstDegiskenim = "Merhaba Const";
console.log(benimConstDegiskenim);


TDZ sayesinde, let ve const kullanırken değişkenlerinizi kullanmadan önce tanımlamanız gerektiğini net bir şekilde anlıyorsunuz. Bu, belirsiz undefined durumlarından çok daha güvenli ve hata ayıklamayı kolaylaştıran bir mekanizma. Modern JavaScript geliştirmede var yerine let ve const kullanmanın en önemli nedenlerinden biri de bu.

Fonksiyon Hoisting'i: Deklarasyonlar ve İfadeler

Değişkenlerde olduğu gibi, fonksiyonlar da hoisting davranışları gösterirler, ancak burada da "fonksiyon deklarasyonu" (function declaration) ve "fonksiyon ifadesi" (function expression) arasında önemli bir fark var.

  • Fonksiyon Deklarasyonları (Function Declarations): Bunlar tamamen hoisting edilir. Yani fonksiyonun tamamı, gövdesiyle birlikte, kapsamının en üstüne taşınır. Bu da demektir ki, fonksiyonu tanımlamadan önce bile çağırabilirsiniz.

CODE
123456
selamVer(); // Çıktı: Merhaba, ben bir fonksiyon deklarasyonuyum!

function selamVer() {
  console.log("Merhaba, ben bir fonksiyon deklarasyonuyum!");
}


Bu, özellikle kodunuzu yapılandırırken esneklik sağlıyor, ancak bazen kodun okunabilirliğini azaltabiliyor.

  • Fonksiyon İfadeleri (Function Expressions): Bir değişkene atanan fonksiyonlardır (anonim veya isimli olabilir). Bu tür fonksiyonlar, atandıkları değişkenin hoisting kurallarına tabidir. Yani var ile tanımlanmış bir değişkene atanmışlarsa, değişken undefined olarak hoisting edilir, fonksiyonun kendisi değil. let veya const ile atanmışlarsa, TDZ kurallarına tabi olurlar.

CODE
1234567891011121314
// var ile fonksiyon ifadesi
// merhabaDe(); // Hata: TypeError: merhabaDe is not a function (çünkü merhabaDe şu an undefined)
var merhabaDe = function() {
  console.log("Merhaba, ben bir fonksiyon ifadesiyim!");
};
merhabaDe(); // Çıktı: Merhaba, ben bir fonksiyon ifadesiyim!

// let ile fonksiyon ifadesi
// naberDe(); // Hata: ReferenceError: Cannot access 'naberDe' before initialization
let naberDe = () => { // Arrow function da bir fonksiyon ifadesidir
  console.log("Naber, ben bir arrow fonksiyonuyum!");
};
naberDe(); // Çıktı: Naber, ben bir arrow fonksiyonuyum!


Bu farklılıkları bilmek, özellikle karmaşık modüllerde veya callback yapılarında hata ayıklarken çok işinize yarayacak. Fonksiyon ifadelerini genellikle let veya const ile kullanmak, daha güvenli ve öngörülebilir bir kod yapısı sağlıyor.

JavaScript Bellek Yönetimine Giriş

Hoisting konusu, aslında bir şekilde JavaScript'in bellek yönetimiyle de kesişiyor. Çünkü değişkenlerin ve fonksiyonların ne zaman "tanımlanmış" sayıldığı, bellekte ne zaman yer ayırdığı ve ne zaman serbest bırakıldığı, dilin genel çalışma prensiplerinin bir parçası. JavaScript, diğer bazı dillere (C, C++) göre geliştiricilere bellek yönetimi konusunda daha az kontrol sunar. Genellikle bu işi otomatik olarak halleder: "Garbage Collection" (Çöp Toplama) mekanizmasıyla.

Bir programın bellek yaşam döngüsü genellikle üç temel aşamadan oluşur:
  • Bellek Ayırma (Allocation): Değişkenler, fonksiyonlar veya objeler oluşturulduğunda, sistem onlara bellek ayırır.
  • Bellek Kullanımı (Usage): Ayrılan belleği okuma, yazma gibi işlemlerle kullanırsınız.
  • Bellek Serbest Bırakma (Release): Kullanılmayan belleğin sistem tarafından geri alınması.
JavaScript'te ilk iki aşamayı biz yönetiriz (değişken tanımlarız, kullanırız), ancak üçüncü aşama, yani bellek serbest bırakma, tamamen JavaScript motorunun sorumluluğundadır. İşte bu noktada çöp toplayıcı devreye giriyor.

Yığın (Stack) ve Yığın Alanı (Heap)

JavaScript'in bellek yönetimi denince akla gelen iki önemli alan var: Stack (Yığın) ve Heap (Yığın Alanı).
  • Stack (Yığın): Bu alan, genellikle sabit boyutlu, ilkel veri tipleri (sayılar, boolean'lar, string'ler, null, undefined, Symbol, BigInt) ve fonksiyon çağrı yığınları için kullanılır. Stack, "Last-In, First-Out" (LIFO) prensibiyle çalışır ve oldukça hızlıdır. Bellek tahsisi ve serbest bırakılması basittir, çünkü öğelerin boyutları derleme zamanında bellidir. Bir fonksiyon çağrıldığında, onun değişkenleri ve parametreleri stack'e itilir; fonksiyon tamamlandığında ise stack'ten çıkarılır.
  • Heap (Yığın Alanı): Bu alan ise daha karmaşık, dinamik boyutlu ve referans tipleri (objeler, diziler, fonksiyonlar) için kullanılır. Heap'te bellek tahsisi ve serbest bırakılması daha karmaşıktır ve dinamik olarak yönetilir. Stack'e göre daha yavaştır, ancak esneklik sunar. Bir obje oluşturduğunuzda, bu obje heap'te yer alır ve stack'teki bir değişken bu objeye bir referans (adres) tutar.
Hoisting'in burada dolaylı bir etkisi var; bir değişkenin bildiriminin erken işlenmesi, o değişken için bellek ayrılma sürecini etkiliyor, özellikle de var durumunda undefined ile başlatılması gibi...

Çöp Toplama (Garbage Collection) Mekanizmaları

Peki, bu otomatik bellek yönetimi nasıl çalışıyor? JavaScript motorları, "çöp toplayıcı" (garbage collector) adı verilen bir mekanizma kullanarak artık kullanılmayan nesneleri tespit eder ve onların kapladığı belleği serbest bırakır. En yaygın çöp toplama algoritması "Mark-and-Sweep" (İşaretle ve Süpür) olarak bilinir.

  • Mark (İşaretleme) Aşaması: Çöp toplayıcı, "kökler" (roots) adı verilen bir dizi başlangıç noktası belirler. Bu kökler genellikle global objeler (örneğin window veya global), mevcut fonksiyon kapsamındaki değişkenler ve aktif çağrı yığındaki referanslardır. Daha sonra, bu köklerden başlayarak erişilebilen tüm nesneleri (yani bir şekilde referans verilen tüm nesneleri) işaretler.
  • Sweep (Süpürme) Aşaması: İşaretleme aşaması tamamlandıktan sonra, çöp toplayıcı bellekteki tüm nesneler arasında gezinir. İşaretlenmemiş olan tüm nesneler, yani köklerden erişilemeyen ve dolayısıyla artık kullanılmayan nesneler, "çöp" olarak kabul edilir ve bellekleri serbest bırakılır.
Önceki çöp toplama yöntemlerinden biri olan "Referans Sayma" (Reference Counting) algoritması da vardı, ancak bu yöntem döngüsel referanslar (circular references) durumunda bellek sızıntılarına yol açtığı için modern JavaScript motorlarında pek kullanılmıyor. Örneğin, A objesi B'ye, B objesi de A'ya referans veriyorsa, her ikisinin de referans sayısı 1 olur ve asla sıfıra düşmez, bu yüzden çöp toplayıcı onları temizleyemez. Mark-and-Sweep bu sorunu aşar çünkü erişilebilirlik ilkesine dayanır.

Hoisting ve Bellek Yönetimi Pratik Çıkarımlar

Şimdi tüm bu teknik bilgiyi pratiğe dökelim. Hoisting ve bellek yönetimi konularını anlamak, daha iyi JavaScript kodu yazmamız için bize ne gibi avantajlar sunuyor?

[list]
  • [b
  • Yanıt vermek için giriş yapmış olmalısınız.

    0 alıntı seçildi