Hoisting, JavaScript'in bir kapsamdaki bildirimleri kod çalışmaya başlamadan önce işlemesidir; sonuç, bildirimler kapsamın en üstüne taşınmış gibi görünür. var değişkenleri undefined değeriyle, fonksiyon bildirimleri ise gövdeleriyle birlikte hazır olur. let, const ve class bildirimleri de kapsamın başından itibaren vardır, ancak bildirim satırına kadar geçici ölü bölgede (TDZ) kalır ve erişilirse ReferenceError fırlatır.
Bu yazıda her bildirim türünün hoisting davranışını, TDZ'nin neden "geçici" olarak adlandırıldığını ve bu kuralların günlük kodda yol açtığı hataları örneklerle inceliyoruz. Üç değişken anahtar kelimesinin genel farkları için var, let ve const farkı yazısına bakabilirsiniz.
Bildirim türlerine göre özet
| Bildirim | Bildirimden önce erişim | Sonuç |
|---|---|---|
| var x = 1 | okunabilir | undefined |
| function f() {} | çağrılabilir | fonksiyon tam olarak hazır |
| var f = function () {} | f() çağrısı | TypeError: f is not a function |
| let x = 1 | okunamaz | ReferenceError (TDZ) |
| const x = 1 | okunamaz | ReferenceError (TDZ) |
| class C {} | new C() yapılamaz | ReferenceError (TDZ) |
| import | modülün her yerinde | bağlamalar ve yan etkiler modül kodundan önce hazır |
var hoisting: bildirim taşınır, değer taşınmaz
var bildirimleri, bulundukları fonksiyon veya betik çalışmadan önce işlenir. Yalnızca bildirim ve undefined ile varsayılan başlatma öne alınır; atama ise kodun bulunduğu satırda yapılır. Bu yüzden değişkeni bildiriminden önce okursanız hata almazsınız, ama değeri undefined olur.
function ornek() {
console.log(sayac); // undefined
var sayac = 111;
console.log(sayac); // 111
}
ornek();Motor bu kodu sanki şöyle yazılmış gibi çalıştırır:
function ornek() {
var sayac;
console.log(sayac); // undefined
sayac = 111;
console.log(sayac); // 111
}
ornek();MDN, bu davranış nedeniyle var bildirimlerinin kapsamın en üstüne yazılmasını önerir. Daha iyisi, var yerine let veya const kullanmaktır.
Fonksiyon bildirimleri tamamen taşınır
function anahtar kelimesiyle yapılan bildirimler değerleriyle birlikte öne alınır. Fonksiyonu tanımlandığı satırdan önce, kapsamın herhangi bir yerinde çağırabilirsiniz.
console.log(topla(2, 3)); // 5
function topla(a, b) {
return a + b;
}Fonksiyon ifadeleri ve ok fonksiyonları ise bu şekilde taşınmaz; davranışları atandıkları değişkenin türüne bağlıdır. var ile atanmışsa değişken adı undefined olarak hazırdır ve çağrı TypeError verir. let veya const ile atanmışsa TDZ nedeniyle ReferenceError alırsınız.
try {
carp(2, 3);
} catch (e) {
console.log(e.name + ": " + e.message); // TypeError: carp is not a function
}
var carp = function (a, b) {
return a * b;
};
try {
bol(6, 3);
} catch (e) {
console.log(e.name + ": " + e.message); // ReferenceError: Cannot access 'bol' before initialization
}
const bol = (a, b) => a / b;Fonksiyon tanımlama biçimlerinin diğer farkları için fonksiyonlar ve arrow function yazısına bakın.
let, const ve geçici ölü bölge (TDZ)

let ve const ile bildirilen değişkenler, bulundukları bloğun başından itibaren kapsamı kaplar; ancak yürütme bildirim satırına gelene kadar başlatılmazlar. Bloğun başı ile bildirim satırı arasındaki bu aralığa geçici ölü bölge (temporal dead zone, TDZ) denir. Bu bölgede değişkeni okumak veya yazmak ReferenceError fırlatır. İlk değer verilmeyen bir let değişkeni bildirim satırına ulaşıldığında undefined ile başlatılır.
MDN'e göre let ve const'ın hoisting'e uğrayıp uğramadığı bir tanım tartışmasıdır; erişim her durumda hata verdiği için bu bildirimler genellikle "hoisting'e uğramayan" bildirimler olarak görülür.
Neden "geçici"?
Bölge, kodun yazıldığı konuma göre değil, çalışma sırasına (zamana) göre belirlenir. Değişkeni kullanan bir fonksiyon bildirimden önce yazılmış olabilir; önemli olan, fonksiyonun bildirim satırı çalıştıktan sonra çağrılmasıdır.
{
const yazdir = () => console.log(mesaj); // tanımlamak sorun değil
let mesaj = "hazır"; // TDZ burada biter
yazdir(); // hazır
}typeof da TDZ'den kurtulamaz
Hiç bildirilmemiş bir değişken için typeof hata vermez ve "undefined" döndürür. TDZ'deki bir değişken için ise typeof bile ReferenceError fırlatır.
console.log(typeof hicTanimlanmamis); // undefined
{
try {
console.log(typeof i);
} catch (e) {
console.log(e.message); // Cannot access 'i' before initialization
}
let i = 10;
}Gölgeleme ile birleşince
Bir blokta bildirilen let veya const, tüm bloğu kapladığı için aynı addaki dış değişkeni bloğun en başından itibaren gölgeler. Diğer dillerden gelenleri en çok şaşırtan durum budur: bildirim satırından önce dıştaki değere erişebileceğinizi düşünürsünüz, oysa içteki değişken zaten TDZ'dedir.
function ornek(x) {
if (true) {
try {
console.log(x);
} catch (e) {
console.log(e.message); // Cannot access 'x' before initialization
}
const x = 2;
console.log(x); // 2
}
}
ornek(1);
function test() {
var deger = 33;
if (deger) {
try {
let deger = deger + 55;
} catch (e) {
console.log(e.name); // ReferenceError
}
}
}
test();İkinci örnekte let deger = deger + 55 satırının sağ tarafındaki deger, dıştaki var değil, henüz başlatılmamış içteki let değişkenidir.
class bildirimleri ve TDZ

class bildirimleri, fonksiyon bildirimlerinden farklı olarak let ve const ile aynı TDZ kurallarına tabidir. Bir sınıfı bildirimden önce kullanmaya çalışırsanız ReferenceError alırsınız. Sınıflar ayrıca betik kökünde global nesne üzerinde özellik oluşturmaz. Sınıf ifadeleri (const C = class {}) de atandıkları değişkenin kurallarını izler.
try {
new Urun();
} catch (e) {
console.log(e.name + ": " + e.message); // ReferenceError: Cannot access 'Urun' before initialization
}
class Urun {
constructor() {
this.ad = "Kalem";
}
}
console.log(new Urun().ad); // KalemSınıf sözdiziminin tamamı class (sınıflar) yazısında anlatılıyor.
var ve function aynı adı paylaştığında
Aynı kapsamda aynı adla hem var hem function bildirimi bulunabilir. İkisi de öne alınır; fonksiyon değeri, var değişkenine ilk atama yapılana kadar geçerli kalır. Kaynak koddaki sıraları bunu değiştirmez.
console.log(typeof a); // function
function a() {}
var a;
console.log(typeof a); // function
var b = 1;
function b() {}
console.log(b); // 1Bu kodlar geçerli olsa da okunması zordur. Aynı adı farklı bildirimler için kullanmaktan kaçının.
Blok içindeki fonksiyon bildirimleri
Strict mode'da bir blok içinde bildirilen fonksiyon o bloğa aittir ve bloğun başına taşınır; blok dışından erişilemez. Strict mode dışında ise blok içi fonksiyon bildirimlerinin davranışı tutarsızdır. MDN bu nedenle blok içinde fonksiyon bildirimini yalnızca strict mode'da yapmanızı önerir.
"use strict";
{
selam(); // Selam
function selam() {
console.log("Selam");
}
}
console.log(typeof selam); // undefinedStrict mode'un getirdiği diğer kurallar için strict mode yazısına bakabilirsiniz.
import bildirimleri
ES modüllerinde import bildirimleri de öne alınır: içe aktarılan adlar modülün her yerinde kullanılabilir ve içe aktarılan modülün yan etkileri, içe aktaran modülün geri kalan kodundan önce çalışır. Bu yüzden import satırlarını dosyanın ortasına yazsanız bile etkileri en başta gerçekleşir. Okunabilirlik için yine de dosyanın başına yazılmaları gelenektir. Ayrıntılar ES modülleri: import ve export yazısında.
Pratik öneriler

- Değişkenleri kullanmadan önce, mümkün olduğunca kapsamın başında bildirin.
varyerineconstveletkullanın; böylece bildirimden önce erişim sessizceundefinedvermek yerine açık bir hataya dönüşür.- Yardımcı fonksiyonları dosyanın sonunda toplamak istiyorsanız
functionbildirimi kullanın; ok fonksiyonu veya fonksiyon ifadesi kullanıyorsanız tanımı çağrıdan önceye yazın. - Sınıfları ve modül düzeyindeki sabitleri, onları kullanan kodun üstünde tanımlayın.
- Blok içinde fonksiyon bildirimi yapacaksanız dosyanın strict mode'da çalıştığından emin olun; ES modülleri ve sınıf gövdeleri zaten strict mode'dadır.
Sık yapılan hatalar
- Ok fonksiyonunu bildiriminden önce çağırmak:
const f = () => ...ile tanımlanan fonksiyon TDZ'dedir; ya çağrıyı aşağı taşıyın ya dafunctionbildirimi kullanın. - Bir sınıfı tanımlandığı dosyanın üst kısmında örneklemek: sınıf bildirimini kullanımdan önceye alın.
- Blokta dış değişkeni okuyup aynı adla
letbildirmek: içteki bildirim dıştakini bloğun başından itibaren gölgeler; farklı bir ad kullanın. varile tanımlı değişkeninundefineddeğerini geçerli bir değer sanmak: bu, bildirimden önce okunduğunun işaretidir;letveyaconstbu hatayı görünür hale getirir.- "Cannot access ... before initialization" mesajını yanlış yorumlamak: değişken tanımlı ama henüz başlatılmamıştır. Hata mesajlarının ayrıntıları için sık görülen hata mesajları yazısına bakın.
Sık Sorulan Sorular
let ve const hoisting'e uğrar mı?
Kapsamın başından itibaren var oldukları için bir anlamda evet, ama bildirim satırına kadar erişilemedikleri için pratikte hayır. MDN bu bildirimlerin genellikle hoisting'e uğramayan bildirimler olarak görüldüğünü belirtir. Pratikte bilmeniz gereken şudur: bildirim satırından önce kullanılamazlar.
Arrow function hoisting'e uğrar mı?
Hayır. Ok fonksiyonu bir ifadedir ve atandığı değişkenin kurallarını izler. const veya let ile atanmışsa bildirimden önce kullanım ReferenceError, var ile atanmışsa TypeError verir.
TDZ hatasını nasıl düzeltirim?
Hata mesajındaki değişkenin bildirimini, değişkeni kullanan satırdan önceye taşıyın ya da kullanımı bildirimden sonraya alın. Gölgeleme varsa içteki değişkene farklı bir ad verin.
Hoisting performansı etkiler mi?
Hoisting kodun anlamını belirleyen bir dil kuralıdır; MDN bunu bir performans konusu olarak ele almaz. Önemli olan, kodun okunabilir ve beklenen sırada çalışmasıdır.
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 · var, let ve const farkı · Scope (kapsam) · Fonksiyonlar ve arrow function · Sık görülen hata mesajları