JavaScript’de “Hoisting” aslında nedir?

Emre AKDAŞ
4 min readAug 13, 2023

--

JavaScript’de fonksiyonları ve değişkenleri tanımlamadan önce kullanabiliyoruz.

test();

function test(){
console.log("test!");
}

Yani, yukarıdaki kod bloğu geçerli bir yazım şekli. console’a “test!” yazdırır. Bu şekilde tanımlamadan önce kullanabilmemize hoisting adı veriliyor. Ama nasıl bu şekilde kullanabiliyoruz?

Çoğu kişi hoisting’i fonksiyonların ve değişkenlerin dosyanın en başına taşınması olarak tanımlar fakat teknik olarak böyle değildir.

Hoisting’in detaylarını açıklamadan önce sizce bu kod bloğu ne çıktısı verecek?

foo();
console.log(bar);

function foo(){
console.log("foo!");
}

var bar = "bar!";
console.log(bar);

Cevaplayamadıysanız, üzülmeyin! Bu örneğe geri döneceğiz. Makalenin sonunda size “Hoisting” kavramının, tam anlamıyla ne olduğunu anlatmayı hedefliyorum.

Hoisting’i daha net anlamamız için bazı ufak JavaScript terimlerine aşina olmamız gerekir. Biraz derine inelim.

Execution Context (Yürütme Bağlamı)

Execution Context, javascript kodununun yürütüldüğü ortamdır. Ortam derken; this anahtar kelimesinin değerini, değişkenlerin ve fonksiyonların değerlerini ayrıca birbirleriyle etkileşimlerini belirtir.

JavaScript’de herhangi bir kod bloğu birer execution context içerisinde yürütülür. 3 tür execution context vardır.

Execution Context Türleri

  1. Global Execution Context
    Bu bağlam javascript kodunun ayrıştırılıp yürütmeye başlandığı, varsayılan yürütme bağlamıdır. Bir kod bloğu herhangi bir fonksiyon içerisinde değilse bu bağlamın içerisinde bulunur. Bir javascript dosyasında yalnızca bir adet global execution context olabilir.
  2. Function Execution Context
    Bir fonksiyon çağrıldığında (tanımlandığında değil) o fonksiyon çağrısına ait bir bağlam oluşturulur. Bu bağlama, function execution context denir.
  3. Eval Execution Context
    eval() fonksiyonu içerisinde bulunan yürütme bağlamıdır.

Execution Context iki aşamada oluşur.

Creation Phase (Oluşturma Aşaması)
Oluşturma aşamasında lexical environment bileşeni oluşturulur.

Lexical Environment iki bölümden oluşur.

  • Environment Record (Ortam Kaydı)
    Environment Record tüm yerel değişkenlerimizin değerlerini ve this anahtar kelimesinin değeri gibi bilgileri depolayan bir nesne.
  • Outer
    Dış lexical environment referans bilgisidir. (Süslü parantezin dışında olan lexical environment’ı ifade eder.)

Bu kısımda “var” ile tanımlanan değişkenler, undefined değeri ile atanarak değişkenlerimiz ve fonksiyonlarımız belleğe kaydedilir.

Bu aşamada kod daha yürütülmeye başlanmamıştır.

Execution Phase (Yürütme Aşaması)
Bu aşamada kod satır satır yürütülür. Fakat bu aşamaya kadar javascript kodumuzda bulunan değişkenlerin gerçek değerlerini bilmez. Kod js engine tarafından satır satır yürütüldüğünde, değişkenlerimize gerçek değerleri atanır. Artık javascript, değişkenlerimizin gerçek değerlerini bilir.

Kodumuzda fonksiyonlarımız belirli bir sırayla çalıştırılır. Fakat bu sıra nasıl yönetiliyor? İşte burada karşımıza call stack kavramı çıkıyor. Ufak bir call stack kavramına da değinmek istiyorum.

Call Stack (Çağrı Yığını)

Çağrı Yığını, last in first out (LIFO) yani son giren ilk çıkar mantığında çalışan, execution contextlerin (fonksiyonların) yürütme sırasını belirten bir mekanizmadır.

Özetle; şu anda hangi fonksiyonun yürütüldüğünü, eğer varsa bu fonksiyon içerisinde hangi fonksiyonların yürütüleceğini takip eden bir mekanizmadır.

Yazının kapsamı dışına çıkmayalım diye bu kadar bilginin yeterli olacağını düşünüyorum.

Şimdi gelelim, öğrendiklerimizle “Hoisting nedir?” sorumuza..

Hoisting nedir?

Hoisting, execution context’in oluşturma aşamasında (creation phase),
var ile tanımlanan değişkenlere varsayılan undefined değeri atanıp fonksiyonların ve değişkenlerin belleğe alınması işlemidir.

Fiziksel olarak kodun asla taşınması değildir.

Dikkat: Sadece değişken tanımlamaları hoisting olur. Değişkenlerin değerleri hoisting olmaz.

foo();
console.log(bar);

function foo(){
console.log("foo!");
}

var bar = "bar!";
console.log(bar);

şimdi bu örneği açıklayalım…

  • global execution context oluşturulur ve call stack’e eklenir.
  • window oluşturulur, this değerini alır (bağlanır)
  • foo isimli fonksiyon belleğe kaydedilir
  • bar isimli değişken, undefined başlangıç değeri ile belleğe kaydedilir (Hoisting bu işleme verilen addır.)
  • kod yürütülmeye başlanır ve foo adlı fonksiyon call stack’e eklenir
  • console’a “foo!” yazılır. foo isimli fonksiyon call stack’ten çıkarılır
  • console’a “undefined” yazılır
  • bar isimli değişken gerçek değeri olan “bar!” değerini alır
  • console’a “bar!” yazılır
  • global execution context call stack’ten çıkarılır ve kodun yürütülmesi böylece biter

Peki kod bu şekilde olsaydı nasıl bir çıktı alırdık?

foo();
console.log(bar);

function foo(){
console.log("foo!");
}

let bar = "bar!";
console.log(bar);

Durun! console’a yapıştırmadan ben söyleyeyim; console’a “foo!” yazılır ve bu şekilde bir hata alırız ve kod 2. satırda çalışmayı durdurur.

neden? akıllara şu soru gelebilir;

let ve const ile tanımlanan değerler hoisting olur mu?

Evet olurlar. Nasıl mı? Bu konuda çok kafa karışıklığı mevcut, gelin açıklayalım.

JavaScript’de tanımlamadığınız bir değişkeni kullanırken nasıl bir hata alıyoruz?

console.log(bar);

bar değişkeni tanımlı değil şeklinde bir hata alıyoruz.

Yukarıda nasıl bir hata alıyorduk

başlatmadan önce “bar” değişkenine erişilemiyor.

Aslında js engine burada bizim foo değişkenimizi biliyor, fakat belleğe kaydedilirken başlangıç değeri olmadan kaydedildiği için erişmeye çalışırken bu hatayı alıyoruz. Bu kodu adımlara dökecek olursak…

  • global execution context oluşturulur ve call stack’e eklenir
  • window oluşturulur, this değerini alır (bağlanır)
  • foo isimli fonksiyon belleğe kaydedilir
  • bar isimli değişken başlangıç değeri olmadan belleğe kaydedilir
  • kod yürütülmeye başlanır ve foo adlı fonksiyon call stack’e eklenir
  • console’a “foo!” yazılır. foo isimli fonksiyon call stack’ten çıkarılır.
  • bar isimli değişkenin başlangıç değeri olmadığı için “başlatmadan önce `bar` değişkenine erişilemiyor” hatası alırız ve kod çalışmayı durdurur.

Özetle;

  • Hoisting, execution context’in oluşturma aşamasında (creation phase), var ile tanımlanan değişkenlere varsayılan undefined değeri atanıp fonksiyonların ve değişkenlerin belleğe alınması işlemidir.
  • Değişkenlerin değerleri hoisting olmaz, sadece tanımlamaları hoisting olur.
  • let ve const ile tanımlanan değişkenler hoisting olmuyor gibi gözüksede aslında başlangıç değerleri yoktur bu sebeple tanımlanmadan önce erişilemezler ve hata alırız.

Atlanmaması gereken bir konu olan “Hoisting” kavramını elimden geldiğince açıklamaya çalıştım.

Umarım aklınızdaki soru işaretlerini giderebilmişimdir. Konuyla alakalı aklınıza takılan bir kısım varsa yorum kısmından sorabilirsiniz. İyi kodlamalar!

--

--