JavaScript öğrenme serüveninizde ilerlerken, yazdığınız kodun bazen beklediğiniz gibi çalışmadığını fark edebilirsiniz. Örneğin, bir değişkeni tanımladığınızı düşünürsünüz ancak sistem size o değişkenin olmadığını (ReferenceError) söyler veya beklediğiniz bir değer yerine undefined (tanımsız) gibi tuhaf bir sonuçla karşılaşırsınız. İşte JavaScript’te yeni başlayanların en çok kafasını karıştıran bu sorunların temelinde yatan iki devasa kavram vardır: Scope (Kapsam) ve Hoisting (Yukarı Taşıma).
Web siteniz için dinamik, hatasız ve profesyonel kodlar yazmak istiyorsanız, JavaScript’in perde arkasında değişkenleri nasıl okuduğunu ve hafızada nasıl tuttuğunu anlamanız gerekir. Bu eğitici ve bol örnekli rehberimizde, JavaScript kodlarınızın çalışma mantığını belirleyen bu iki temel kuralı en ince ayrıntısına kadar öğreneceğiz.
1. Scope (Kapsam) Nedir?
Programlama dünyasında Scope, bir değişkenin kodun hangi bölümlerinden erişilebilir (okunabilir ve yazılabilir) olduğunu tanımlayan kurallar bütünüdür. Bir değişken “kapsam içindeyse” (in scope), o değişkene ulaşabilir ve onu kullanabilirsiniz; eğer değişken “kapsam dışındaysa” (out of scope), ona ulaşamazsınız ve JavaScript size bir hata fırlatır.
Scope kavramını bir ev olarak düşünebilirsiniz. Evin bahçesindeki bir eşyayı (küresel) herkes görebilir. Ancak kilitli bir odanın (fonksiyon veya blok) içindeki bir eşyayı, sadece o odanın içindeki kişiler görebilir. JavaScript’te 3 temel kapsam türü vardır:
A. Global Scope (Küresel Kapsam)
Herhangi bir fonksiyonun veya bloğun (süslü parantezlerin {}) dışında, dosyanın en üst seviyesinde tanımlanan değişkenler Global Scope‘a aittir. Bu değişkenlere, programın neresinde olursanız olun (fonksiyonların veya döngülerin içinden bile) her yerden erişebilirsiniz.
Örnek:
let kureselDegisken = "Ben her yerden erişilebilirim!";
function testFonksiyonu() {
// Fonksiyonun içinden küresel değişkene ulaşıyoruz
console.log(kureselDegisken);
}
testFonksiyonu(); // Çıktı: Ben her yerden erişilebilirim!
Küresel değişkenler kullanışlı görünse de, büyük projelerde isim çakışmalarına (farklı yerlerde aynı isimde değişken kullanma) yol açabileceği için her şeyi küresel alanda tanımlamak kötü bir uygulama (bad practice) olarak kabul edilir.
B. Local / Function Scope (Yerel / Fonksiyon Kapsamı)
Bir fonksiyonun içinde tanımlanan değişkenler sadece o fonksiyonun içine aittir ve bunlara Yerel Değişkenler (Local Variables) denir. Bu değişkenlere fonksiyonun dışından ulaşılamaz; fonksiyonun çalışması bittiğinde bu değişkenler bellekten silinir.
Örnek:
function gizliOda() {
let gizliMesaj = "Bu mesaj sadece fonksiyonun içinde yaşar!";
console.log(gizliMesaj); // Başarılı şekilde çalışır
}
gizliOda();
console.log(gizliMesaj); // Hata: ReferenceError: gizliMesaj is not defined
Yukarıdaki örnekte görüldüğü gibi, gizliMesaj değişkenine fonksiyonun dışından ulaşmaya çalıştığımızda hata alırız.
C. Block Scope (Blok Kapsamı) ve ES6 Devrimi
Geçmişte (ES6 / ECMAScript 2015 öncesinde), JavaScript’te süslü parantezler { } (örneğin bir if koşulu veya for döngüsü) yeni bir kapsam yaratmıyordu. var kelimesiyle tanımlanan değişkenler sadece fonksiyon kapsamını tanıyordu.
Modern JavaScript ile birlikte hayatımıza giren let ve const kelimeleri ise Blok Kapsamına (Block Scope) sahiptir. Yani, bu kelimelerle tanımladığınız bir değişken, sadece onu çevreleyen süslü parantezler { } içinde yaşar.
Örnek (var ile eski kullanım):
if (true) {
var eskiDegisken = "Dışarı sızarım!";
}
console.log(eskiDegisken); // Çıktı: Dışarı sızarım!
Yukarıdaki örnekte var, if bloğunu umursamaz ve değişken dışarı sızar.
Örnek (let veya const ile modern kullanım):
if (true) {
let modernDegisken = "Sadece bu bloğun içindeyim.";
}
console.log(modernDegisken); // Hata: ReferenceError: modernDegisken is not defined
let ve const kullandığımızda değişken, bloğun dışına sızmaz ve kodumuz çok daha güvenli hale gelir.
2. Hoisting (Yukarı Taşıma) Nedir?
Scope kavramını anladıysak, şimdi kodun nasıl okunduğuna geçebiliriz. Hoisting (Yukarı Taşıma), JavaScript motorunun, kodu çalıştırmadan önce dosyayı tarayıp tüm değişken ve fonksiyon tanımlamalarını (declarations) bulundukları kapsamın (scope) en üstüne taşıması prensibidir.
Bu, diğer birçok programlama dilinde olmayan, JavaScript’e özgü eşsiz (ve bazen kafa karıştırıcı) bir özelliktir.
var ve Hoisting Davranışı
Hoisting mantığını anlamanın en iyi yolu var kelimesine bakmaktır. JavaScript, bir değişkenin sadece tanımlanmasını (declaration) yukarı taşır, içine atanan değeri (initialization) yukarı taşımaz.
Şu koda bakalım:
console.log(benimSayim);
var benimSayim = 5;
Diğer dillerde bu kod, değişken henüz tanımlanmadığı için programı çökertecek bir hata verir. Ancak JavaScript’te bu kod hata vermez; sadece ekrana undefined (tanımsız) yazdırır.
Peki neden? Çünkü JavaScript kodu çalıştırmadan önce sahne arkasında (hafızada) şu şekilde düzenler:
var benimSayim; // Sadece tanım (declaration) en başa gizlice taşındı (Hoisting)
console.log(benimSayim); // İçinde henüz değer olmadığı için "undefined" yazar
benimSayim = 5; // Değer ataması (initialization) yerinde kaldı
Değişken tanımlanmadan önce kullanılmaya çalışıldığı için sonuç undefined olur.
let, const ve Temporal Dead Zone (Geçici Ölü Bölge)
“Madem Hoisting var, neden let ve const kullandığımızda hata alıyoruz?” diye sorabilirsiniz. let ve const ile oluşturulan değişkenler de aslında kapsamın en üstüne taşınır (hoisted). Ancak var‘dan farklı olarak, kodun o satırı çalışıp onlara bir değer atanana kadar kullanımlarına izin verilmez. Bu aradaki erişilemeyen zamana Geçici Ölü Bölge (Temporal Dead Zone – TDZ) denir.
console.log(sayi); // Hata: ReferenceError: Cannot access 'sayi' before initialization
let sayi = 10;
Eğer kodunuzda bir değişkene tanımlanmadan önce ulaşmaya çalışırsanız, let veya const size ReferenceError vererek kodunuzdaki bu mantık hatasını düzeltmeniz için sizi uyarır. var‘ın yaptığı gibi gizlice undefined verip hataları maskelemez; bu yüzden modern web geliştirmede let kullanılması şiddetle tavsiye edilir.
Fonksiyonlarda Hoisting (Function Hoisting)
Hoisting sadece değişkenler için değil, fonksiyonlar için de geçerlidir. Ancak burada önemli bir ayrım vardır: Normal fonksiyon tanımlamaları (Function Declarations) içlerindeki kod bloğuyla birlikte tamamen yukarı taşınır.
Örnek:
selamla(); // Fonksiyonu daha yazmadan önce çağırıyoruz!
function selamla() {
console.log("Merhaba, Hoisting sayesinde çalışıyorum!");
}
// Çıktı: Merhaba, Hoisting sayesinde çalışıyorum!
Bu sayede, kodunuzun düzenini sağlarken fonksiyonlarınızı dosyanın en altına yazıp, en üstünde çağırabilirsiniz.
Not: Eğer fonksiyonu bir değişkene atayarak oluşturursanız (Arrow Functions veya Function Expressions), bu bir değişken Hoisting’i gibi davranır ve fonksiyonu öncesinde çağıramazsınız!
Sonuç ve Yeni Başlayanlar İçin En İyi Uygulamalar (Best Practices)
JavaScript’te Scope (Kapsam), değişkenlerinizin nerede yaşayıp nerede öldüğünü belirlerken; Hoisting (Yukarı Taşıma) kodunuzun bilgisayar tarafından hafızaya alınma ve okunma sırasını ifade eder.
Bu iki konuyu kusursuz yönetmek için web geliştiricilerinin uyması gereken altın kurallar şunlardır:
- Artık
varkullanmayın:varyerine, modern ve blok kapsamlı olanletve değişmeyecek veriler içinconstkullanın. Bu, sizi hoisting kaynaklı gizli hatalardan (undefinedbuglarından) kurtaracaktır. - Değişkenlerinizi en başta tanımlayın: Hoisting’in arka planda yapmasına güvenmek yerine, değişkenlerinizi kullanacağınız kapsamın en başında siz tanımlayın.
- Küresel (Global) değişkenleri en aza indirin: Her fonksiyonun dışarıdaki verilere bağımlı olmasını engellemek için, verileri fonksiyonların içinde yerel (local) olarak tanımlayın.
Scope ve Hoisting mantığını kavradığınızda, yazdığınız JavaScript kodlarının kontrolü tamamen sizin elinize geçer ve hata ayıklamak (debugging) çok daha kolay bir hale gelir. Artık değişkenlerinizin nerede çalıştığını bildiğinize göre, bir sonraki projenizde çok daha sağlam mimariler kurmaya hazırsınız!





