Skip to main content

JavaScript’te Değişkenler, Scope ve Hoisting’i Anlamak

Giriş

Değişkenler, birçok programlama dilinin temel parçalarından biridir ve yeni başlayanların öğrenmesi gereken en önemli konulardandır.

JavaScript’te değişkenlerin çeşitli özellikleri vardır ve isimlendirme sırasında uyulması gereken kurallar bulunmaktadır..
Bir değişken tanımlamak için kullanılan üç anahtar kelime vardır: var, let ve const.
Her biri, kodun değişkeni nasıl yorumlayacağını farklı şekilde etkiler.

Bu rehberde değişkenlerin ne olduğunu, nasıl tanımlanıp isimlendirileceğini öğreneceğiz.
Ayrıca var, let ve const arasındaki farklara yakından bakacağız.

Bunun yanında hoisting kavramının etkilerini ve değişkenlerin davranışında global ve local scope’un önemini de inceleyeceğiz.

Değişkenleri Anlamak

Değişken, değerleri saklamak için kullanılan isimlendirilmiş bir kap gibidir.
Bir bilgiyi birden fazla kez kullanmamız gerekiyorsa, onu bir değişkende tutabilir ve daha sonra tekrar çağırabilir ya da değiştirebiliriz.

JavaScript’te bir değişkenin içindeki değer herhangi bir veri tipi olabilir: sayı (number), metin (string) veya nesne (object) gibi. Daha fazlası için JavaScript Data Types dökümantasyonuna göz atabilirsiniz.

ECMAScript 2015 (ES6) standardından önce, JavaScript’te değişken tanımlamanın tek yolu var anahtar kelimesini kullanmaktı.
Bu yüzden eski kodlarda ve kaynaklarda genellikle sadece var ile tanımlanmış değişkenler görürsünüz.

Birazdan var, let ve const arasındaki farkları ayrı bir bölümde inceleyeceğiz.

Şimdilik, değişken kavramını göstermek için var kullanabiliriz.
Aşağıdaki örnekte bir değişken tanımlayıp ona bir değer atayacağız.

// username değişkenine "kadir_dev" string değerini ata
var username = "kadir_dev";

Bu ifade birkaç bölümden oluşur:

  • var anahtar kelimesiyle değişkenin tanımlanması
  • Değişkenin adı (identifier), username
  • Atama işlemi, = işareti ile gösterilir
  • Atanan değer, "kadir_dev"

Artık username değişkenini kod içinde kullanabiliriz.
JavaScript, username’in "kadir_dev" string değerini temsil ettiğini hatırlayacaktır.

// Değişkenin değeriyle eşit olup olmadığını kontrol et
if (username === "kadir_dev") {
console.log(true);
}
Output	
true

Daha önce bahsettiğimiz gibi, değişkenler herhangi bir JavaScript veri tipini temsil edebilir.

Bu örnekte string, number, object, Boolean ve null değerlerle değişkenler tanımlayacağız.

// Farklı türlerde değişkenlerin atanması
var name = "Kadir";
var spartans = 300;
var kingdoms = [ "memeliler", "kuşlar", "balıklar" ];
var poem = { roses: "kırmızı", violets: "mavi" };
var success = true;
var nothing = null;

console.log kullanarak, belirli bir değişkenin içinde hangi değerin saklandığını görebiliriz.

// spartans değişkenini konsola gönder
console.log(spartans);
Output
300

Değişkenler, bellekte veri saklar ve bu verilere daha sonra erişilip değiştirilebilir.
Bir değişken yeniden atanabilir ve ona yeni bir değer verilebilir.

Aşağıdaki basit örnekte, bir şifrenin bir değişkende saklanıp daha sonra nasıl güncellenebileceğini göreceğiz.

// password değişkenine değer ata
var password = "gizli123";

// Değişken değerini yeni bir değerle güncelle
password = "gizli456";

console.log(password);
Output
gizli456

Gerçek bir programda şifreler büyük ihtimalle güvenli bir şekilde veritabanında saklanır.
Bu örnek ise sadece bir değişkenin değerini nasıl güncelleyebileceğimizi göstermek için yapıldı.

Önce password değişkeninin değeri gizli123 idi, sonra biz onu gizli456 olarak yeniden atadık.
Bundan sonra JavaScript, password için yeni değeri tanıyacaktır.

Değişkenlere İsim Vermek

JavaScript’te değişken isimlerine identifier (tanımlayıcı) denir.
Daha önce JavaScript’te Söz Dizimi ve Kod Yapısını Anlamak bölümünde isimlendirme kurallarını konuşmuştuk.
Burada özet olarak tekrar hatırlayalım:

  • Değişken isimleri sadece harfler (a-z), rakamlar (0-9), dolar işareti $ ve alt çizgi _ karakterlerinden oluşabilir.
  • Değişken isimlerinde boşluk (tab veya space) kullanılamaz.
  • Hiçbir değişken ismi rakamla başlayamaz.
  • JavaScript’te ayrılmış bazı anahtar kelimeler (reserved keywords) değişken ismi olarak kullanılamaz.
  • Değişken isimleri büyük/küçük harf duyarlıdır.

JavaScript’te yaygın olarak camel case (ör. camelCase) kullanılır.
Yani ilk kelime küçük harfle başlar, sonraki her kelimenin ilk harfi büyük yazılır ve arada boşluk olmaz.
var veya let ile tanımlanan değişkenler genellikle bu kurala uyar.

Sabit değerleri tutan değişkenler (yani const ile tanımlananlar) ise genellikle tamamen büyük harflerle yazılır.

Başta bu kurallar fazla gibi gelebilir, ama kısa sürede alışkanlık haline gelir ve doğru, standart değişken isimleri yazmak doğal bir refleks olur.

var, let ve const Arasındaki Farklar

JavaScript’te değişken tanımlamak için üç farklı anahtar kelime vardır: var, let ve const.
Bu da dile fazladan bir katman karmaşıklık ekler.

Bu üçü arasındaki farklar; scope (kapsam), hoisting ve yeniden atama (reassignment) kavramlarına dayanır.

Anahtar KelimeKapsam (Scope)HoistingYeniden AtanabilirYeniden Tanımlanabilir
varFonksiyon scopeEvetEvetEvet
letBlok scopeHayırEvetHayır
constBlok scopeHayırHayırHayır

Peki kendi programlarımızda bu üçünden hangisini kullanmalıyız?

Genel olarak kabul edilen yaklaşım:

  • const: Mümkün olduğunca kullanılmalı.
  • let: Döngülerde veya değerin yeniden atanması gerektiğinde tercih edilir.
  • var: Eski (legacy) kodlarla çalışmadığınız sürece genellikle kullanılmaz.

Değişken Scope (Kapsamı)

JavaScript’te scope, kodun geçerli bağlamını ifade eder ve bir değişkenin erişilebilir olup olmadığını belirler.
İki tür scope vardır: global ve local.

  • Global değişkenler: Bir bloğun dışında tanımlanır.
  • Local değişkenler: Bir bloğun içinde tanımlanır.

Aşağıdaki örnekte bir global değişken oluşturacağız.

// Global bir değişken tanımla
var creature = "kurt";

Değişkenlerin yeniden atanabileceğini öğrenmiştik.
Local scope kullanarak, dıştaki bir değişkenle aynı isme sahip yeni bir değişken tanımlayabiliriz. Bu durumda orijinal değişkenin değeri değişmez.

Aşağıdaki örnekte bir global species değişkeni tanımlayacağız.
Fonksiyon içinde ise aynı isimle bir local değişken oluşturacağız.

Konsola yazdırdığımızda, değişkenin değerinin scope’a (kapsama) göre nasıl farklı olduğunu ve orijinal değerin değişmediğini göreceğiz.

// Global bir değişken tanımla
var species = "insan";

function transform() {
// Local (fonksiyon scope) bir değişken tanımla
var species = "kurtadam";
console.log(species); // fonksiyon içindeki değeri yazdırır
}

// Global ve local değişkenleri konsola yazdır
console.log(species); // insan
transform(); // kurtadam
console.log(species); // insan
Output	
insan
kurtadam
insan

Bu örnekte local değişken fonksiyon scope’unda.
var anahtar kelimesiyle tanımlanan değişkenler her zaman function-scoped olur; yani fonksiyonları ayrı bir scope olarak kabul eder.
Bu yüzden local değişken, global scope’tan erişilemez.

Buna karşılık, yeni anahtar kelimeler olan let ve const, block scope’a sahiptir.
Yani fonksiyon bloklarının yanı sıra if, for, while gibi tüm bloklar kendi kapsamını oluşturur.

Fonksiyon scope ile block scope arasındaki farkı göstermek için bir if bloğu içinde let kullanarak yeni bir değişken tanımlayacağız.

var fullMoon = true;

// Global bir değişken tanımla
let species = "insan";

if (fullMoon) {
// Blok scope bir değişken tanımla
let species = "kurtadam";
console.log(`Dolunay var. Lupin şu anda bir ${species}.`);
}

console.log(`Dolunay yok. Lupin şu anda bir ${species}.`);
Dolunay var. Lupin şu anda bir kurtadam.
Dolunay yok. Lupin şu anda bir insan.

Bu örneğin sonucunda hem global değişken hem de block-scoped değişken aynı değeri almış olur: kurtadam.
Bunun nedeni, var ile yeni bir local değişken oluşturmak yerine, aynı scope içinde aynı değişkenin üzerine yazmanızdır.
var, if bloğunu yeni ve farklı bir scope olarak tanımaz.

Bu yüzden, değişkenlerinizi block-scoped olacak şekilde (let veya const ile) tanımlamanız önerilir.
Böylece istemeden değişken değerlerini ezme ihtimaliniz azalır.

Hoisting

Şimdiye kadar verdiğimiz örneklerde genelde bir değişkeni var ile tanımlayıp, hemen ardından bir değer atadık.
Tanımlama ve değer atamasından sonra bu değişkene erişebiliyor veya yeni bir değer atayabiliyoruz.

Eğer bir değişkeni tanımlamadan ve değer vermeden önce kullanmaya çalışırsak, sonuç undefined döner.

// Değişkeni tanımlamadan önce kullanmayı dene
console.log(x);

// Değişkene değer ata
var x = 100;
Output 
undefined

Ancak var anahtar kelimesini kullanmazsak, değişkeni tanımlamış olmayız, sadece değer atamış oluruz.
Bu durumda JavaScript bir ReferenceError döndürür ve script’in çalışmasını durdurur.

// Değişkeni tanımlamadan önce kullanmayı dene
console.log(x);

// var kullanmadan değer ataması yap
x = 100;
Output
ReferenceError: x is not defined

Bunun nedeni hoisting’dir.
Hoisting, JavaScript’in değişken ve fonksiyon tanımlamalarını bulundukları scope’un en üstüne taşıma davranışıdır.

Ancak burada sadece tanımlama (declaration) yukarı taşınır, değer ataması (initialization) taşınmaz.
Bu yüzden ilk örnekte sonuç undefined döner.

Aşağıda yazdığımız kodun JavaScript tarafından nasıl yorumlandığını daha net görebilirsiniz.

// Bizim yazdığımız kod
console.log(x);
var x = 100;

// JavaScript’in yorumladığı hali
var x;
console.log(x); // undefined
x = 100;

JavaScript, x değişkenini script çalıştırılmadan önce belleğe kaydetti.
Ama değer ataması yapılmadan çağrıldığı için sonuç 100 değil, undefined oldu.
Yine de bu bir ReferenceError üretmedi ve script’in çalışmasını durdurmadı.

Aslında var ifadesi fiziksel olarak yukarıya taşınmaz, ama hoisting kavramını anlamak için bu şekilde düşünmek faydalıdır.
Bu davranış sorunlara yol açabilir, çünkü kodu yazan geliştirici çıktının 100 olmasını beklerken aslında undefined döner.

Hoisting’in nasıl öngörülemeyen sonuçlara yol açabileceğini bir sonraki örnekte de göreceğiz:

// Global scope'ta x değişkenini tanımla
var x = 100;

function hoist() {
// Normalde sonucu etkilememesi gereken bir koşul
if (false) {
var x = 200;
}
console.log(x);
}

hoist(); // undefined
Output
undefined

Bu örnekte x değişkenini globalde 100 olarak tanımladık.
if koşuluna bağlı olarak x değeri 200 olabilirdi, fakat koşul false olduğu için aslında değeri etkilememesi gerekiyordu.

Buna rağmen x, hoisting nedeniyle hoist() fonksiyonunun en üstüne taşındı ve değeri undefined oldu.

Bu tür öngörülemeyen davranışlar programlarda hatalara yol açabilir.
let ve const ise block scope’a sahip oldukları için bu şekilde hoist olmazlar.
Aşağıda göreceğiniz gibi daha güvenilir bir davranış sergilerler.

// Global scope'ta x değişkenini tanımla
let x = true;

function hoist() {
// Fonksiyon scope içinde x tanımla
if (3 === 4) {
let x = false;
}
console.log(x);
}

hoist(); // true
Output 
true

Aynı değişkeni birden fazla kez tanımlamak var ile mümkündür, fakat let ve const ile bu durum hata verir.

// var ile tanımlanan bir değişkeni yeniden tanımlamayı dene
var x = 1;
var x = 2;

console.log(x); // 2
Output
2
// let ile tanımlanan bir değişkeni yeniden tanımlamayı dene
let y = 1;
let y = 2;

console.log(y);
Uncaught SyntaxError: Identifier 'y' has already been declared

Özetle:
var ile tanımlanan değişkenler, JavaScript’in hoisting mekanizması nedeniyle etkilenebilir.
Bu da kodda undefined değerlerin ortaya çıkmasına yol açabilir.

let ve const ise bu sorunu çözer.

  • Tanımlanmadan önce kullanılmaya çalışıldığında hata verirler.
  • Aynı değişkeni birden fazla kez tanımlamaya da izin vermezler.

Bu sayede daha güvenilir ve öngörülebilir bir kod yazımı sağlanır.

Sabitler (Constants)

Birçok programlama dilinde sabitler (constants) bulunur.
Bunlar, değerleri değiştirilemeyen değişkenlerdir.

JavaScript’te const anahtar kelimesi bu mantıkla çalışır ve ona atanan değer yeniden atanamaz.

Yaygın bir kullanım kuralı olarak, tüm const değişkenleri büyük harflerle yazılır.
Bu sayede diğer değişkenlerden kolayca ayırt edilebilirler.

Aşağıdaki örnekte SPECIES adında bir sabiti const ile tanımlıyoruz.
Bu değişkeni yeniden atamaya çalışmak hata verecektir.

// const ile sabit bir değer ata
const SPECIES = "insan";

// Yeniden değer atamayı dene
SPECIES = "kurtadam";

console.log(SPECIES);
Output
Uncaught TypeError: Assignment to constant variable.

const ile tanımlanan değişkenler yeniden atanamaz.
Bu yüzden mutlaka tanımlandıkları anda bir değer verilmelidir.
Aksi halde hata oluşur.

// Bir const tanımla ama değer atama
const TODO;

console.log(TODO);
Output
Uncaught SyntaxError: Missing initializer in const declaration

Programlamada değiştirilemeyen değerlere immutable, değiştirilebilenlere ise mutable denir.

Her ne kadar const ile tanımlanan değişkenler yeniden atanamasalar da, aslında mutable olabilirler.
Yani const ile tanımlanan bir nesnenin veya dizinin özelliklerini değiştirmek mümkündür.

// İki özelliğe sahip bir CAR nesnesi oluştur
const CAR = {
color: "kırmızı",
price: 12000
}

// CAR nesnesinin bir özelliğini değiştir
CAR.price = 20000;

console.log(CAR);
// { color: "mavi", price: 20000 }
Output
{ color: 'kırmızı', price: 20000 }

const kullanmak, hem sizin gelecekteki halinize hem de projede sizinle çalışan diğer programcılara, bu değişkenin yeniden atanmasının amaçlanmadığını açıkça gösterir.

Eğer bir değişkenin ileride değiştirilmesini bekliyorsanız, onu let ile tanımlamak daha doğru bir seçim olacaktır.

Sonuç

Bu rehberde bir değişkenin ne olduğunu, değişken isimlendirme kurallarını ve değerlerin nasıl yeniden atanabileceğini öğrendik.
Ayrıca scope ve hoisting kavramlarını inceledik, var anahtar kelimesinin bazı kısıtlamalarını gördük ve let ile const’un bu sorunları nasıl çözdüğünü öğrendik.