JavaScript'te var fonksiyon kapsamlıdır, aynı kapsamda tekrar bildirilebilir ve bildirildiği satırdan önce okunduğunda undefined verir. let ve const ise blok kapsamlıdır, aynı kapsamda tekrar bildirilemez ve bildirimden önce erişildiğinde ReferenceError fırlatır. const ek olarak ilk değer ister ve yeniden atanamaz. Modern kodda varsayılan seçim const, değeri değişecek değişkenler için let olmalıdır.
Bu yazıda üç anahtar kelimenin farklarını çalıştırılabilir örneklerle inceliyoruz: kapsam, yeniden bildirim, yeniden atama, global nesne üzerindeki etki ve geçici ölü bölge (TDZ). Bildirimlerin kod çalışmadan önce nasıl işlendiğini ise hoisting ve TDZ yazısında ayrıca ele aldık.
Kısa karşılaştırma

| Özellik | var | let | const |
|---|---|---|---|
| Kapsam | fonksiyon (yoksa modül veya global) | blok | blok |
| Aynı kapsamda yeniden bildirim | izinli | SyntaxError | SyntaxError |
| Yeniden atama | izinli | izinli | TypeError |
| İlk değer zorunlu mu | hayır | hayır | evet |
| Bildirimden önce erişim | undefined | ReferenceError (TDZ) | ReferenceError (TDZ) |
| Betik kökünde globalThis özelliği oluşturur mu | evet | hayır | hayır |
| Tek başına if gövdesi olabilir mi | evet | hayır | hayır |
Kapsam: fonksiyon mu, blok mu?
var ile tanımlanan değişken, onu saran en yakın fonksiyon gövdesine (veya sınıfların static başlatma bloğuna) aittir. Hiçbiri yoksa modül ya da global kapsama düşer. if, for, try...catch veya düz bir { } bloğu var için yeni bir kapsam açmaz. let ve const ise içinde bulundukları bloğa bağlıdır.
function varOrnegi() {
var x = 1;
{
var x = 2; // aynı değişken
console.log(x); // 2
}
console.log(x); // 2
}
function letOrnegi() {
let x = 1;
{
let x = 2; // farklı değişken
console.log(x); // 2
}
console.log(x); // 1
}
varOrnegi();
letOrnegi();Aynı fark döngülerde de görülür. for başlığında var ile tanımlanan sayaç döngü bittikten sonra da erişilebilir kalır; let ile tanımlanan sayaç ise yalnızca döngünün içinde yaşar.
for (var i = 0; i < 3; i++) {}
console.log(i); // 3
for (let j = 0; j < 3; j++) {}
console.log(typeof j); // undefined (j döngü dışında tanımlı değil)Döngü içindeki geri çağrılarda let farkı
Farkın en çok hissedildiği yer, döngü içinde daha sonra çalışacak fonksiyonlar oluşturduğunuz durumdur. var ile tüm fonksiyonlar tek bir değişkeni paylaşır ve döngü bittiğinde o değişkenin son değerini görür. let ise her yinelemede yeni bir bağlama (binding) oluşturur, böylece her fonksiyon kendi değerini hatırlar.
const varIle = [];
for (var i = 0; i < 3; i++) {
varIle.push(() => i);
}
console.log(varIle.map((f) => f())); // [ 3, 3, 3 ]
const letIle = [];
for (let k = 0; k < 3; k++) {
letIle.push(() => k);
}
console.log(letIle.map((f) => f())); // [ 0, 1, 2 ]Bu davranışın arkasındaki mekanizma closure'dır; ayrıntıları closure nedir yazısında bulabilirsiniz. Zamanlayıcı ile çalışan geri çağrıların ne zaman çalıştığını ise event loop yazısı açıklıyor.
Yeniden bildirim

var ile aynı adı ikinci kez bildirmek hata vermez, strict mode'da bile. İlk değer verilmeyen ikinci bildirim mevcut değeri silmez. let ve const ise aynı kapsamda başka hiçbir bildirimle (let, const, var, function, class veya import) yan yana duramaz. Bu bir SyntaxError olduğu için kod hiç çalışmaya başlamaz.
var a = 1;
var a = 2;
console.log(a); // 2
var a;
console.log(a); // 2 (değer korunur)
// Aşağıdaki satırlar açılırsa dosya hiç çalışmaz:
// let b = 1;
// let b = 2; // SyntaxError: Identifier 'b' has already been declared
// const c = 1;
// var c = 2; // SyntaxError: Identifier 'c' has already been declaredİç içe bloklarda aynı adı kullanmak ise yeniden bildirim sayılmaz; içteki değişken dıştakini gölgeler (shadowing). Dikkat edilmesi gereken bir durum switch ifadesidir: tüm case dalları tek bir blok paylaştığı için iki dalda aynı adla let yazmak hata verir. Her dalı kendi süslü parantezine almak sorunu çözer.
const durum = 1;
switch (durum) {
case 0: {
let mesaj = "sıfır";
console.log(mesaj);
break;
}
case 1: {
let mesaj = "bir";
console.log(mesaj); // bir
break;
}
}Yeniden atama ve const
const bir değişken adını bir değere kalıcı olarak bağlar. Bu bağlamayı değiştirmeye çalışmak çalışma anında TypeError fırlatır. İlk değer vermeden const yazmak ise doğrudan sözdizimi hatasıdır: const SABIT; satırı "Missing initializer in const declaration" mesajıyla reddedilir.
const PORT = 3000;
try {
PORT = 8080;
} catch (e) {
console.log(e.name + ": " + e.message); // TypeError: Assignment to constant variable.
}Önemli ayrıntı şudur: const değeri değil, bağlamayı sabitler. Değer bir nesne veya dizi ise içeriği değiştirilebilir. Nesnenin kendisini de dondurmak istiyorsanız Object.freeze() kullanabilirsiniz. Dondurulmuş nesnede değişiklik denemesi strict mode dışında sessizce yok sayılır, strict mode'da ise TypeError fırlatır.
const ayar = { port: 80 };
ayar.port = 8080; // izinli: nesnenin içeriği değişiyor
console.log(ayar.port); // 8080
const liste = [];
liste.push("a");
console.log(liste); // [ 'a' ]
const sabitAyar = Object.freeze({ port: 80 });
sabitAyar.port = 8080; // strict mode dışında sessizce yok sayılır
console.log(sabitAyar.port); // 80Bildirimden önce erişim ve TDZ
var bildirimi kod çalışmadan önce işlenir ve değişken undefined değeriyle hazır bekler. let ve const de kapsamın başından itibaren var olur, ancak bildirim satırına ulaşılana kadar başlatılmamış durumdadır. Bu aralığa geçici ölü bölge (temporal dead zone, TDZ) denir ve bu sırada değişkene erişmek ReferenceError fırlatır.
{
console.log(eski); // undefined
try {
console.log(yeni);
} catch (e) {
console.log(e.message); // Cannot access 'yeni' before initialization
}
var eski = 1;
let yeni = 2;
}TDZ'nin neden "geçici" olarak adlandırıldığı, typeof ile etkileşimi ve class bildirimlerindeki karşılığı hoisting ve TDZ yazısında ayrıntılı örneklerle anlatılıyor.
Global nesne üzerindeki etki
Klasik betik (script) modunda en üst düzeyde var ile tanımlanan değişken global nesnenin silinemeyen bir özelliği olur. let ve const ise global kapsamda olsalar bile globalThis üzerinde özellik oluşturmaz. Node.js'in CommonJS modüllerinde ve ES modüllerinde ise en üst düzeydeki bildirimler zaten modüle aittir ve global nesneye eklenmez.
// Betik modunda (ör. tarayıcı konsolunda veya node -e ile):
var x = 1;
let y = 2;
console.log(globalThis.x, globalThis.y); // 1 undefinedYukarıdaki kodu bir dosyaya yazıp Node.js ile çalıştırırsanız dosya CommonJS modülü olarak yüklendiği için iki değer de undefined çıkar. Modül kapsamı hakkında daha fazlası için ES modülleri ve scope (kapsam) yazılarına bakabilirsiniz.
Tek satırda birden fazla bildirim ve destructuring
Üç anahtar kelime de virgülle ayrılmış bir bildirim listesi kabul eder. Buradaki virgüller virgül operatörü, eşittir işaretleri de atama operatörü değildir; listenin bir parçasıdır. Listede sonra gelen değişkenin ilk değeri, kendisinden önce tanımlanan değişkeni kullanabilir. Eşittir işaretinin sol tarafına bir destructuring kalıbı da yazabilirsiniz; böylece bir nesneden veya diziden aynı anda birden fazla const değişkeni oluşturursunuz.
const kenar = 4, alan = kenar * kenar;
console.log(alan); // 16
const kisi = { ad: "Deniz", yas: 30 };
const { ad, yas } = kisi;
console.log(ad, yas); // Deniz 30Destructuring sözdiziminin tamamı destructuring ve spread yazısında anlatılıyor.
Hangisini ne zaman kullanmalısınız?

- Varsayılan olarak
const: MDN'in kod stili rehberi de dahil olmak üzere pek çok stil rehberi, kapsamı içinde yeniden atanmayan değişkenler içinconstönerir. Okuyan kişi değerin değişmeyeceğini hemen anlar. - Değer değişecekse
let: sayaçlar, birikimli toplamlar ve koşula göre atanan değişkenler için. varkullanmayın: MDN, blok kapsamına uymaması gibi şaşırtıcı davranışları nedeniylevar'ın modern kodda önerilmediğini belirtir. Eski kodu okurken bilmeniz yeterli.
Sık yapılan hatalar
constile tanımlanan nesnenin değişmez olduğunu sanmak: yalnızca bağlama sabittir; içerik içinObject.freeze()gerekir.constdeğişkene sonradan değer vermeye çalışmak: ilk değer bildirimle birlikte verilmelidir.switchdallarında aynı adlaletyazmak: hercasegövdesini{ }içine alın.- Tek satırlık
ifgövdesinde bildirim yapmak:if (kosul) let a = 1;birSyntaxErrorverir, çünkületveconstdeyim değil bildirim başlatır. - Döngüde
varile geri çağrı oluşturmak: tüm fonksiyonlar aynı değişkeni görür;letkullanın.
Sık Sorulan Sorular
const ile tanımlanan dizi neden değiştirilebiliyor?
const değişkenin başka bir değere bağlanmasını engeller, değerin kendisini dondurmaz. Diziye push() ile eleman eklemek aynı diziyi değiştirir; değişken hâlâ aynı diziyi gösterdiği için hata oluşmaz. Diziye yeni bir dizi atamaya çalışırsanız TypeError alırsınız.
let ile tanımlanan değişken hoisting'e uğrar mı?
Bu bir tanım tartışmasıdır. let değişkeni kapsamın başından itibaren vardır, ancak bildirim satırına kadar erişilemez. MDN bu yüzden let ve const bildirimlerinin genellikle hoisting'e uğramayan bildirimler olarak görüldüğünü belirtir.
İç blokta aynı adla tekrar let yazmak hata mı?
Hayır. Farklı bir blok yeni bir kapsam açtığı için içteki değişken dıştakinden bağımsızdır ve onu gölgeler. Hata yalnızca aynı kapsamda iki bildirim olduğunda ortaya çıkar.
Eski bir projede var'ları let ile değiştirmek güvenli mi?
Çoğu durumda evet, ancak kör bir bul-değiştir yapmayın. Değişken bildirildiği bloğun dışında kullanılıyorsa, bildirimden önce okunuyorsa veya aynı ad tekrar bildiriliyorsa davranış değişir ya da sözdizimi hatası oluşur. Her değişikliği kapsamına bakarak yapın.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: JavaScript öğrenme rehberi · Hoisting ve TDZ · Scope (kapsam) · Closure nedir · Strict mode