JavaScript Değişkenleri: let ve const, Block Scope ve TDZ
let ve const ile değişken tanımlıyor, yeniden atama ile mutasyon farkını, block scope'u, isimlendirme kurallarını, var'ın neden emekliye ayrıldığını ve TDZ'yi örneklerle görüyoruz.
Sürüm notu: Bu yazıdaki kodlar ES2026 ve güncel tarayıcılar (Chrome, Edge, Firefox, Safari) içindir.
Önce Tahmin Et
Elimizde bir alışveriş sepeti var. const ile tanımladık, içine bir şey ekledik, sonra sepeti tümden değiştirmeye çalıştık:
const cart = ["apple"];
cart.push("pear");
console.log(cart);
cart = ["banana"];
console.log(cart);
Bu kod konsola ne yazdırır? İki satır mı, bir satır mı, hiç mi? Hata çıkacaksa hangi satırda? Okumaya devam etmeden önce bir tahmin yap. const'un aslında neyi sabitlediğini konuştuktan sonra cevaba döneceğiz.
Küçük bir pratik not: bu yazıdaki örnekler aynı isimleri tekrar tekrar kullanıyor. Konsolda "has already been declared" hatası alırsan sayfayı yenileyip örneği öyle yapıştır.
Değişken: Bir Değere İsim Vermek
Program çalışırken bir değeri saklayıp sonra tekrar kullanmak istersin: kullanıcının adı, sepetteki ürün sayısı, oyundaki skor. Değişken, bir değere taktığın isimdir. Bunu bir depodaki kutulara etiket yapıştırmak gibi düşün. Değer kutunun içindeki şeydir, değişken ise üstündeki etiket. Etiketi okuyunca hangi kutuya bakman gerektiğini bilirsin.
Modern JavaScript'te değişken tanımlamanın iki yolu var: let ve const. İkisi de ES2015 ile geldi. Önce let'e bakalım. Bunu tarayıcının konsoluna yapıştırıp dene:
let score = 0;
console.log(score);
score = 10;
score = score + 5;
console.log(score);
let user;
console.log(user);
Konsolda sırasıyla 0, 15 ve undefined göreceksin. let score = 0 satırı iki iş yapıyor: score adında bir değişken tanımlıyor ve ona ilk değerini veriyor. Sonraki satırlarda başına let yazmadan yeni değer atıyoruz; buna yeniden atama denir. score = score + 5 satırını sağdan sola oku: önce mevcut değere 5 eklenir, çıkan sonuç tekrar score'a yazılır.
Son iki satır da önemli. Değer vermeden tanımladığın bir let değişkeni boş kalmaz, undefined değerini alır. Bu "henüz bir değer atanmadı" demektir. undefined'ı bir sonraki yazıda veri tipleriyle birlikte daha yakından tanıyacağız.
const: Bir Kez Bağla, Bırak
const ile tanımladığın değişkene tanımlarken bir değer verirsin ve o isim bir daha başka bir değere bağlanamaz:
const birthYear = 1990;
console.log(birthYear);
birthYear = 1991;
İlk satır 1990 yazdırır, ardından TypeError: Assignment to constant variable. hatası gelir. Motor sana "bu isme yeni bir değer atayamazsın" diyor. Değersiz bir const da yazamazsın; const city; satırı daha çalışmadan SyntaxError: Missing initializer in const declaration verir. Mantıklı: sonradan değiştiremeyeceğin bir şeyi boş bırakmanın anlamı yok.
Peki hangisini ne zaman kullanacaksın? Bugün yaygın kabul gören kural basit: varsayılan olarak const yaz, değerin gerçekten değişmesi gerekiyorsa let'e geç. Bu sana koddan bir mesaj da verir. Bir dosyada let gördüğünde "dikkat, bu değer ileride değişecek" diye okursun; const gördüğünde aklından çıkarabilirsin.
Yeniden Atama mı, Mutasyon mu?
Baştaki soruya dönelim. Konsolda önce ["apple", "pear"] görürsün, ardından TypeError: Assignment to constant variable. hatası gelir ve son console.log hiç çalışmaz. Peki const bir sepete nasıl armut eklememize izin verdi?
Çünkü const değeri değil, ismin bağlantısını sabitler. Etiket örneğine dönersek: const ile etiketi kutuya yapıştırıp kilitliyorsun. Etiketi söküp başka bir kutuya yapıştıramazsın, ama kutunun kapağını açıp içine bir şey koyabilirsin. cart.push("pear") kutunun içine bir şey koyuyor; buna mutasyon denir. cart = ["banana"] ise etiketi bambaşka bir kutuya taşımaya çalışıyor; bu yeniden atamadır ve const buna izin vermez.
Aynısı object'ler için de geçerli:
const settings = { theme: "dark" };
settings.theme = "light";
console.log(settings.theme);
Bu kod sorunsuz çalışır ve "light" yazdırır. Array'leri ve object'leri ilerleyen yazılarda detaylandıracağız; şimdilik şunu akılda tut: const "bu isim hep aynı şeyi gösterecek" demektir, "bu şeyin içi asla değişmeyecek" demek değildir. İçeriği gerçekten dondurmanın yollarını da ileride göreceğiz.
Block Scope: Süslü Parantezin Sınırı
Bir değişkenin kodun neresinden görülebildiğine scope denir. let ve const block scope'ludur: süslü parantez { } içinde tanımladığın değişken yalnızca o parantezin içinde yaşar. Bunu konsolda dene:
const city = "Ankara";
{
const city = "Izmir";
const district = "Alsancak";
console.log(city);
}
console.log(city);
console.log(district);
Çıktı sırasıyla Izmir, Ankara ve ReferenceError: district is not defined. Blok bir oda gibidir. Odanın içinde tanımladığın district odadan dışarı çıkamaz; kapıdan çıktığın anda o isim yok olur. İçerideki city ise dışarıdaki city'yi bozmaz, sadece oda içindeyken onu gölgeler. Buna shadowing denir. Motor bir ismi ararken önce bulunduğu bloğa bakar, orada yoksa bir dış bloğa çıkar.
Bu kural tek başına duran bloklarla sınırlı değil. if ve döngülerin süslü parantezleri de birer bloktur ve aynı şekilde çalışır; onları ilerleyen yazılarda göreceğiz. Block scope'un faydası şu: bir değişkeni ihtiyaç duyduğun en dar alanda tanımlarsan, kodun başka bir köşesinde aynı ismi kullanan biriyle çakışmazsın.
İsimlendirme Kuralları ve camelCase
Değişken isimleri birkaç katı kurala uymak zorunda. Harf, rakam, $ ve _ kullanabilirsin ama isim rakamla başlayamaz. Boşluk ve tire de olmaz. JavaScript büyük-küçük harfe duyarlıdır: name ile Name iki ayrı değişkendir. Bunların hepsi geçerli:
let userName = "mert";
let $price = 100;
let _temp = true;
let number2 = 7;
const MAX_ATTEMPTS = 3;
Şunlar ise hata verir; her birini ayrı ayrı dene:
let 2number = 7;
let user-name = "mert";
let class = "A";
İlki rakamla başladığı, ikincisi tire içerdiği için SyntaxError verir; motor tireyi çıkarma işareti sanar. Üçüncüsü ise ayrılmış bir kelimeye çarpıyor. let, const, class, if, for, return, function, new, typeof gibi kelimeler dilin kendisine ait; değişken adı olarak kullanamazsın.
Kuralların ötesinde bir de gelenek var. JavaScript'te değişkenler camelCase yazılır: ilk kelime küçük, sonraki her kelimenin ilk harfi büyük. username yerine userName, total_amount yerine totalAmount. Programın başında bir kez belirlenen ve hiç değişmeyen ayar değerleri için de MAX_ATTEMPTS gibi büyük harf ve alt çizgi kullanılır.
Türkçe karakterler teknik olarak geçerlidir, let şehir çalışır. Yine de farklı klavyelerde yazması zor olduğu ve ekip içinde sorun çıkarabildiği için ASCII harflerde kalmanı öneririm. En önemlisi de anlamlı isim seçmek: x yerine productCount, d yerine birthDate. Kodu bir ay sonra okuyacak kişi büyük ihtimalle sensin.
var: Neden Artık Kullanmıyoruz?
Eski kodlarda, eski eğitimlerde ve internetteki pek çok cevapta üçüncü bir yol göreceksin: var. ES2015'ten önce elimizdeki tek seçenek buydu. Bugün yeni kodda kullanmıyoruz, ama nedenini bilmen gerekiyor, çünkü onunla karşılaşacaksın. Konsolda dene:
var message = "first";
var message = "second";
console.log(message);
{
var leak = "I escaped the block";
}
console.log(leak);
İki sorun birden görünüyor. Birincisi, aynı ismi hiçbir uyarı almadan ikinci kez tanımlayabildin. Uzun bir dosyada başkasının değişkenini farkında olmadan ezmenin en kolay yolu bu. let ile aynısını yapsaydın SyntaxError: Identifier 'message' has already been declared alırdın.
İkincisi, var block scope tanımaz. Süslü parantez içinde tanımladığın leak dışarıdan rahatça okunuyor. var'ın sınırı blok değil fonksiyondur; buna function scope denir ve fonksiyonları ilerleyen yazılarda detaylandıracağız. Bir de en dışta tanımlanan var değişkenleri window object'inin property'si olur: var color = "blue" yazdıktan sonra window.color da "blue" verir. let ve const bunu yapmaz.
Kısacası var, kuralları gevşek eski bir kiracı gibi: odasından taşar, aynı isimle ikinci kez kayıt yaptırır, kimse de ses çıkarmaz. Eski kodda gördüğünde ne yaptığını anla, yeni kodda let ve const kullan.
TDZ'ye İlk Bakış
var'ın bir tuhaflığı daha var ve let/const'un en ilginç davranışı tam burada ortaya çıkıyor. Değişkeni tanımlamadan önce okumaya çalışalım:
console.log(oldOne);
var oldOne = "with var";
console.log(newOne);
let newOne = "with let";
İlk console.log hata vermez, undefined yazdırır. Motor, kodu çalıştırmadan önce var ile tanımlanan isimleri tarar ve onları baştan undefined değeriyle hazırlar. Değer atama ise ancak o satıra gelindiğinde olur. İkinci console.log ise ReferenceError: Cannot access 'newOne' before initialization verir. Firefox'ta mesaj biraz farklı yazılır ama hata türü yine ReferenceError'dır.
Bunun nedeni Temporal Dead Zone, kısaca TDZ. Motor let ve const isimlerini de bloğun başında tanır, ama onlara değer vermez ve tanımlama satırına gelene kadar dokunmana izin vermez. Bloğun başından tanımlama satırına kadar uzanan bu bölge, değişkenin "ölü bölgesi"dir.
Bir tiyatrodaki rezerve koltuk gibi düşün. Koltuğun üstünde adının yazılı olduğu bir kart var, yani koltuk senin. Ama sen gelip oturana kadar oraya kimse bir şey koyamaz, koltuğa bakıp "burada kim oturuyor" diye sorana da "henüz gelmedi" cevabı verilir. var ise o koltuğa sen gelmeden bir boş karton bırakır; bakan kişi hata almaz ama yanlış bir şey görür. Hangisi daha güvenli, ortada. Hoisting ve TDZ'nin bütün ayrıntılarını ilerleyen yazılarda detaylandıracağız; şimdilik kuralı bilmen yeter: let ve const'u tanımlamadan önce kullanamazsın.
Sık Düşülen Tuzaklar
const'u "değeri asla değişmez" sanmak. Gördüğümüz gibi const bir array'in ya da object'in içini korumaz, yalnızca ismin başka bir şeye bağlanmasını engeller.
Konsolun hoşgörüsüne aldanmak. Chrome'un konsolu, ayrı ayrı girdiğin satırlarda aynı ismi tekrar let ile tanımlamana kolaylık olsun diye izin verir. Aynı iki satır bir dosyada ya da tek seferde çalıştırıldığında SyntaxError verir. Konsolda gördüğün her rahatlığın gerçek kodda geçerli olduğunu varsayma.
Tanımlamayı unutmak. total = 5 yazıp başına let ya da const koymazsan, normal modda tarayıcı sessizce global bir değişken oluşturur. Daha önce gördüğümüz strict mode bunu ReferenceError'a çevirir; hatayı erkenden yakalamak için iyi bir sebep.
Aynı blokta aynı ismi iki kez tanımlamak. Bu bir SyntaxError olduğu için, daha önce gördüğümüz gibi dosyanın hiçbir satırı çalışmaz. Tek bir isim çakışması bütün script'i durdurabilir.
Her şeye let yazmak. Çalışır, ama okuyana yanlış sinyal verir. Değişmeyecek bir değeri const ile tanımla ki let gördüğün yerde gerçekten dikkat etmen gerektiğini bil.
Küçük Bir Deneme
★☆☆ Adını ve doğum yılını const ile, yaşını da let ile tanımla. Yaşı 2026 - birthYear ile hesapla ve üçünü tek bir console.log ile yazdır. Sonra doğum yılını değiştirmeyi dene ve aldığın hatayı oku.
★★☆ Çalıştırmadan önce çıktıyı tahmin et:
let x = 1;
{
let x = 2;
x = x + 10;
console.log(x);
}
console.log(x);
★★★ Aşağıdaki kodda birden fazla sorun var. Konsola yapıştırınca hangi hatayı önce göreceğini tahmin et ve nedenini açıkla. Sonra kodu let, const ve doğru isimlerle baştan yaz; adet gerçekten değişmesi gereken bir değer olsun.
var total = 0;
var price-1 = 40;
const quantity = 2;
quantity = 3;
total = price-1 * quantity;
Bu yazı boyunca değişkenlere sayılar, metinler, array'ler koyduk ama değerlerin kendisinden hiç bahsetmedik. Bir sonraki yazıda JavaScript'in veri tiplerine bakacağız: yedi primitive tip, object'ler, typeof ve herkesi en az bir kez şaşırtan typeof null tuzağı.