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

BildirimBildirimden önce erişimSonuç
var x = 1okunabilirundefined
function f() {}çağrılabilirfonksiyon tam olarak hazır
var f = function () {}f() çağrısıTypeError: f is not a function
let x = 1okunamazReferenceError (TDZ)
const x = 1okunamazReferenceError (TDZ)
class C {}new C() yapılamazReferenceError (TDZ)
importmodülün her yerindebağ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.

javascript
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:

javascript
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.

javascript
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.

javascript
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, const ve geçici ölü bölge (TDZ): Neden "geçici"?, typeof da TDZ'den kurtulamaz, Gölgeleme ile birleşince

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.

javascript
{
  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.

javascript
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.

javascript
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 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.

javascript
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); // Kalem

Sı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.

javascript
console.log(typeof a); // function
function a() {}
var a;
console.log(typeof a); // function

var b = 1;
function b() {}
console.log(b); // 1

Bu 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.

javascript
"use strict";
{
  selam(); // Selam
  function selam() {
    console.log("Selam");
  }
}
console.log(typeof selam); // undefined

Strict 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

Pratik öneriler: Değişkenleri kullanmadan önce, mümkün olduğunca kapsamın başında bildirin., var yerine const ve let kullanın; böylece bildirimden önce erişim sessizce...
  • Değişkenleri kullanmadan önce, mümkün olduğunca kapsamın başında bildirin.
  • var yerine const ve let kullanın; böylece bildirimden önce erişim sessizce undefined vermek yerine açık bir hataya dönüşür.
  • Yardımcı fonksiyonları dosyanın sonunda toplamak istiyorsanız function bildirimi 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 da function bildirimi 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 let bildirmek: içteki bildirim dıştakini bloğun başından itibaren gölgeler; farklı bir ad kullanın.
  • var ile tanımlı değişkenin undefined değerini geçerli bir değer sanmak: bu, bildirimden önce okunduğunun işaretidir; let veya const bu 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ı