Ders 04 / 21
Değişken Bildirimleri
Fonksiyon kapsamlı ve blok kapsamlı bildirimlerin farkı, yeniden bildirim kuralları, sabit bağlamanın kapsamı ve döngü değişkeninin bağlanması.
İçindekiler
Önceki ders, yeni yeteneklerin eskisinin yerine değil yanına eklendiğini söyledi. Bunun en somut örneği bildirim türleridir: dilin ilk halinde tek bir bildirim vardı, ES2015 ile iki bildirim daha eklendi ve eskisi kaldırılmadı. Üçü aynı işi yapmaz.
Programlama Temelleri kursundaki Kapsam ve Yaşam Süresi dersinde kapsam, bir adın görüldüğü program bölgesi olarak tanımlanmıştı. Bu ders o tanımı JavaScript’e uygular ve üç bildirimin hangi bölgeyi seçtiğini gösterir. Fark yazım tercihi değildir; adın nerede görüleceğini, ne zaman erişilebilir olacağını ve yeniden bağlanıp bağlanamayacağını belirler.
İki Kapsam Birimi
var ile bildirilen ad fonksiyon kapsamlıdır: içinde bulunduğu fonksiyonun
tamamında görülür. Bildirimin bir blok içinde yapılmış olması bir şeyi değiştirmez.
let ve const ile bildirilen ad blok kapsamlıdır: yalnızca içinde bulunduğu süslü
ayraç çiftinde ve onun içindeki bloklarda görülür.
function kapsamDenemesi() { if (true) { var varDegisken = "var ile"; let letDegisken = "let ile"; console.log(varDegisken, "/", letDegisken); } console.log(varDegisken); console.log(typeof letDegisken); } kapsamDenemesi();
var ile / let ile var ile undefined
Blok kapandıktan sonra var ile bildirilen ad hâlâ erişilebilir; let ile bildirilen ad
görünmez olmuştur. Üçüncü satırın typeof ile yazılmasının nedeni budur: doğrudan
okumak hata verirdi.
Hatanın kendisini görmek, ayrımın keskinliğini gösterir:
if (true) { let esik = 20; console.log(esik); } try { console.log(esik); } catch (hata) { console.log(hata.name + ": " + hata.message); }
20 ReferenceError: esik is not defined
Blok kapsamı, adın ömrünü ihtiyaç duyulan bölgeyle sınırlar. Programlama Temelleri kursundaki gölgeleme ve ad çakışması tartışması burada doğrudan işe yarar: bir adın görünürlük alanı ne kadar darsa, kazara ezilme olasılığı o kadar azdır.
Yeniden Bildirim
Üç bildirim, aynı adın ikinci kez bildirilmesine farklı tepki verir.
var, aynı kapsamda aynı adın yeniden bildirilmesine izin verir. İkinci bildirim yeni
bir ad yaratmaz; var olan ada yeni bir değer atar:
var esik = 20; var esik = 25; console.log(esik);
25
Bu esneklik, uzun bir fonksiyonun ortasında farkında olmadan aynı adı ikinci kez
kullanmayı sessiz bir hataya çevirir. let ve const aynı kapsamda yeniden bildirimi
reddeder:
let esik = 20; let esik = 25;
Bu dosya çalıştırıldığında hiçbir satırı yürütülmez:
SyntaxError: Identifier 'esik' has already been declared
Hatanın türüne dikkat edin. Bu bir çalışma zamanı hatası değil, ayrıştırma hatasıdır; ilk derste kurulan ayrım burada işe yarıyor. Aynı adın iki kez bildirilmesi, kaynağın tamamı okunurken saptanabilir bir durumdur, bu yüzden program hiç başlamaz.
const bildirimi ayrıca başlangıç değeri ister:
const esik;
SyntaxError: Missing initializer in const declaration
Bunun nedeni sonraki bölümde görülecek: sabit bir ad yalnızca bir kez bağlanabildiği için, bağlanmamış bir sabit hiçbir zaman değer alamazdı.
Sabit Bildirimin Sınırı
const, bağlamayı sabitler. Programlama Temelleri kursundaki Değişkenler ve Bağlama
dersinde ad ile değer arasındaki ilişki “bağlama” olarak adlandırılmıştı; const tam
olarak o ilişkinin değişmesini engeller.
const esik = 20; try { esik = 21; } catch (hata) { console.log(hata.name + ": " + hata.message); }
TypeError: Assignment to constant variable.
Sabitlenen şey bağlamadır, değerin içeriği değil. Bağlanan değer değiştirilebilir bir nesneyse, nesnenin içeriği değişebilir:
const kayit = { istasyon: "A1", sicaklik: 21.5 }; kayit.sicaklik = 22; console.log(kayit);
{ istasyon: 'A1', sicaklik: 22 }
Ad hâlâ aynı nesneye bağlıdır; değişen, nesnenin bir özelliğidir. Aynı ada başka bir nesne bağlamak ise reddedilir:
const kayit = { istasyon: "A1", sicaklik: 21.5 }; try { kayit = { istasyon: "A2", sicaklik: 19 }; } catch (hata) { console.log(hata.name + ": " + hata.message); }
TypeError: Assignment to constant variable.
Bu ayrım terminolojiyle örtüşür: const bir sabit (constant) bağlama kurar, değeri
değiştirilemez (immutable) yapmaz. Nesnelerin içeriğini gerçekten dondurmanın yolu
ayrı bir işlemdir ve bu kursun kapsamı dışındadır.
Döngü Değişkeninin Bağlanması
Kapsam farkının en çok sonuç doğurduğu yer döngü başlığıdır. var ile bildirilen döngü
değişkeni tüm döngü boyunca tek bir bağlamadır; let ile bildirilen değişken her
turda yeniden bağlanır.
Fark, döngü içinde üretilen fonksiyonlarda görünür:
const varIsleri = []; for (var i = 0; i < 3; i++) { varIsleri.push(() => i); } console.log(varIsleri.map((f) => f())); const letIsleri = []; for (let j = 0; j < 3; j++) { letIsleri.push(() => j); } console.log(letIsleri.map((f) => f()));
[ 3, 3, 3 ] [ 0, 1, 2 ]
İlk döngüde üç fonksiyon da aynı i bağlamasını okur. Döngü bittiğinde o bağlamanın
değeri 3’tür — koşul bu değerde yanlışa döndüğü için döngü sonlanmıştır. Fonksiyonlar
daha sonra çağrıldığında hepsi 3 görür.
İkinci döngüde her tur için ayrı bir j bağlaması oluşur ve her fonksiyon kendi turunun
bağlamasını okur. Bu davranış let bildiriminin döngü başlığına özgü bir kuralıdır:
tur başında yeni bağlama yaratılır ve önceki turun değeriyle başlatılır.
Ok fonksiyonu sözdizimi (() => i) burada yalnızca kısa yazım için kullanıldı;
davranışı Ok Fonksiyonları dersinde ele alınacak. Fonksiyonun kendisini yaratıldığı
kapsamdaki adlara bağlı tutmasına kapanış (closure) denir; Programlama Temelleri
kursunda tanımlanmıştı ve bir sonraki kursta ayrıntılanacaktır.
Koleksiyon üzerinde dönerken const kullanılabilir, çünkü her tur yeni bir bağlama
kurar ve tur içinde değişken yeniden atanmaz:
const sicakliklar = [21.5, 19.75, 23]; for (const deger of sicakliklar) { console.log(deger); }
21.5 19.75 23
Ölçüm Betiğine Uygulama
Kursun ekseni olan betik, bu kurallara göre yeniden yazıldığında hangi adın neden hangi bildirimle verildiği açıklanabilir hale gelir:
const sicakliklar = [21.5, 19.75, 23, 18.25]; const esik = 20; let toplam = 0; let esikUstu = 0; for (const deger of sicakliklar) { toplam += deger; if (deger > esik) { const fark = deger - esik; esikUstu += 1; console.log("esik asildi, fark:", fark); } } console.log("ortalama:", toplam / sicakliklar.length); console.log("esik ustu okuma:", esikUstu);
esik asildi, fark: 1.5 esik asildi, fark: 3 ortalama: 20.625 esik ustu okuma: 2
Üç karar birlikte alındı. Yeniden bağlanmayacak adlar (sicakliklar, esik, tur
değişkeni deger, blok içi fark) const ile bildirildi. Yalnızca birikeç görevindeki
iki ad (toplam, esikUstu) let aldı. fark adı yalnızca if bloğunda gerektiği
için orada bildirildi; blok kapsamı sayesinde döngünün geri kalanında görünmez.
Bu yazım kuralı kurs boyunca korunacak: yeniden bağlanmayan her ad const ile, yeniden
bağlanan ad let ile bildirilir. var yalnızca davranışı gösterilirken kullanılacak.
Gerekçe estetik değildir — bir adın yeniden bağlanıp bağlanmadığı bildirimden okunduğunda,
kodu izlemek için tüm gövdeyi taramak gerekmez.
Özet
varfonksiyon kapsamlıdır;letveconstblok kapsamlıdır ve süslü ayraç çiftinin dışında görünmez.- Aynı kapsamda
varyeniden bildirilebilir;letveconstyeniden bildirimi ayrıştırma aşamasındaSyntaxErrorile reddeder. constbağlamayı sabitler, değeri değiştirilemez yapmaz: nesnenin özellikleri değişebilir, ada başka nesne bağlanamaz.varile bildirilen döngü değişkeni tek bir bağlamadır;lether turda yeni bağlama kurar ve döngü içinde üretilen fonksiyonlar bu farkı gösterir.- Kurs kuralı: yeniden bağlanmayan ad
const, birikeç görevindeki adletalır.
Sonraki Adım
Kapsam sorusunun yarısı yanıtlandı: bir adın nerede görüldüğü. Diğer yarısı ne zaman
erişilebilir olduğudur. Bildirimden önceki satırda aynı ada dokunmak var ile
undefined verirken let ile hata üretir; ikisi de bildirimin “yukarı taşındığı”
gerçeğiyle açıklanır. Sonraki ders sözcüksel kapsamı, yukarı taşımayı ve bildirim öncesi
bölgenin kurallarını ele alacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.