İçeriğe geç
academia.sh

Ders 05 / 21

Adlandırma ve Kapsam Kuralları

Ad kuralları ve ayrılmış sözcükler, sözcüksel kapsamın çözümlenmesi, gölgeleme, yukarı taşıma ve zamansal ölü bölge.

İçindekiler

Önceki ders bir adın nerede görüldüğünü belirledi. Bu ders iki soruyu daha yanıtlar: aynı ad birden çok kapsamda bildirilmişse hangisi geçerlidir, ve bir ad bildirim satırından önce okunursa ne olur?

İki sorunun yanıtı da tek bir ilkeden çıkar: JavaScript sözcüksel kapsam (lexical scope) kullanır. Bir adın hangi bağlamaya karşılık geldiği, kodun yazıldığı yere bakılarak belirlenir; çalışma anında kimin kimi çağırdığına bakılmaz. Programlama Temelleri kursundaki Kapsam ve Yaşam Süresi dersinde tanımlanan bu ilke, aşağıdaki bütün davranışların kaynağıdır.

Ad Kuralları

Bir ad harfle, alt çizgiyle veya dolar işaretiyle başlar; devamında rakam da bulunabilir. Adlar büyük–küçük harfe duyarlıdır: esik ile Esik iki farklı addır.

const olcumSayisi = 4;
const OLCUM_ESIGI = 20;
const $ad = "A1";
const _gecici = true;
console.log(olcumSayisi, OLCUM_ESIGI, $ad, _gecici);
console.log("esik" === "Esik");
4 20 A1 true
false

Dilbilgisi Latin harfleri dışındaki harfleri de kabul eder, ancak kurstaki tüm adlar ASCII karakterlerle yazılmıştır. Gerekçe pratiktir: görsel olarak birbirine benzeyen ama farklı kod noktalarına karşılık gelen karakterler, ayırt edilmesi güç hatalar üretir. Bilgisayarlar Nasıl Çalışır kursundaki Karakter Kodlamaları dersinde tanımlanan kod noktası kavramı burada doğrudan işe yarar: iki ad ancak kod noktası dizileri aynıysa aynıdır.

Dilin ayırdığı sözcükler ad olarak kullanılamaz:

const class = 1;
SyntaxError: Unexpected token 'class'

Bu da bir ayrıştırma hatasıdır; dosya hiç çalışmaz. Ayrılmış sözcükler listesi standartta yazılıdır ve class, const, return, typeof, new gibi dil yapılarını içerir.

Adlandırma sözleşmesi olarak kursta değişken ve fonksiyon adları küçük harfle başlar ve sonraki sözcükler büyük harfle sürer (olcumSayisi); yeniden hesaplanmayacak eşik ve ayar değerleri büyük harfle yazılır (OLCUM_ESIGI). Sözleşme dilin kuralı değildir; tutarlılık için seçilmiştir.

Sözcüksel Kapsam

Bir ad okunduğunda önce içinde bulunulan kapsama bakılır. Ad orada yoksa onu çevreleyen kapsama, sonra onun çevresine bakılır; en dış kapsamda da bulunamazsa ReferenceError verilir. Bu zincir, kodun yazılı yapısından çıkarılır.

const esik = 20;

function disKapsam() {
  const esik = 25;
  function icKapsam() {
    return esik;
  }
  return icKapsam();
}

function cagiran() {
  const esik = 30;
  return disKapsam();
}

console.log(disKapsam());
console.log(cagiran());
console.log(esik);
25
25
20

İkinci satır belirleyicidir. cagiran fonksiyonunun içinde esik adı 30’a bağlıdır ve disKapsam oradan çağrılmıştır. Buna rağmen sonuç 25’tir: icKapsam, adı çağıranın kapsamında değil, kendi yazıldığı yerin çevreleyen kapsamında arar. Çağrı zinciri kapsam zincirini değiştirmez.

Alternatif model — adın çağıranın kapsamında aranması — dinamik kapsam (dynamic scoping) olarak adlandırılır ve bazı kabuk dillerinde bulunur. JavaScript bu modeli kullanmaz. Ayrım pratik bir sonuç doğurur: bir fonksiyonun hangi adları görebildiği, onu okuyan kişi tarafından yalnızca kaynağa bakılarak belirlenebilir.

Gölgeleme

İç kapsamda dış kapsamdakiyle aynı adın bildirilmesine gölgeleme (shadowing) denir. Yukarıdaki örnekte disKapsam içindeki esik, en dıştaki esik adını gölgeler. Dış bağlama yok olmaz; yalnızca o bölgede erişilemez hale gelir — son satırın 20 yazması bunu gösterir.

Gölgeleme bilinçli kullanıldığında yararlıdır: dar bir bölgede aynı kavramın farklı bir değerini taşımak, yeni bir ad uydurmaktan okunaklıdır. Kazara yapıldığında ise dış değerin neden kullanılmadığını açıklamak güçleşir. Kural basittir: gölgeleme yapılıyorsa bilinçli olmalı.

Yukarı Taşıma

Bildirimler, kaynakta yazıldıkları satırda değil, kapsamın başında işlenir. Bu davranışa yukarı taşıma (hoisting) denir. Üç bildirim türü aynı biçimde taşınmaz ve fark tam olarak burada ortaya çıkar.

var ile bildirilen ad, kapsamın başında yaratılır ve undefined ile başlatılır. Bildirim satırına gelene kadar okunabilir; okunduğunda undefined verir:

console.log(typeof varAdi);
var varAdi = 20;
console.log(varAdi);
undefined
20

let ve const ile bildirilen ad da kapsamın başında yaratılır, ama başlatılmaz. Yaratılma anı ile bildirim satırı arasındaki bölgede ada dokunmak hata verir:

try {
  console.log(letAdi);
} catch (hata) {
  console.log(hata.name + ": " + hata.message);
}
let letAdi = 25;
console.log(letAdi);
ReferenceError: Cannot access 'letAdi' before initialization
25

Bu bölgeye zamansal ölü bölge (temporal dead zone) denir. İletiyi dikkatle okumak gerekir: hata “ad bulunamadı” demiyor, “başlatılmadan önce erişilemez” diyor. Ad kapsamda vardır; henüz bir değere bağlanmamıştır. Önceki dersteki esik is not defined iletisiyle karşılaştırıldığında fark nettir — o ad hiç bildirilmemişti, bu ad bildirilmiş ama başlatılmamıştır.

Ölü bölge, gölgelemeyle birleştiğinde sezgiye aykırı görünen bir sonuç üretir:

const esik = 20;
function hatali() {
  try {
    console.log(esik);
    const esik = 25;
  } catch (hata) {
    console.log(hata.name + ": " + hata.message);
  }
}
hatali();
ReferenceError: Cannot access 'esik' before initialization

Dıştaki esik adı 20’ye bağlı olmasına rağmen okunamadı. Nedeni sözcüksel kapsamın tanımıdır: fonksiyon gövdesinde esik bildirildiği için, o gövdedeki her esik başvurusu yerel bağlamayı gösterir. Yerel bağlama ise henüz başlatılmamıştır. Dış bağlamaya erişmenin yolu yoktur; gölgeleme, bildirim satırından önce de geçerlidir.

Fonksiyon Bildirimleri

Fonksiyon bildirimleri, adlarıyla birlikte gövdeleriyle de yukarı taşınır. Bu yüzden bildirim satırından önce çağrılabilirler:

console.log(ortalama([21.5, 19.75, 23, 18.25]));

function ortalama(dizi) {
  let toplam = 0;
  for (const d of dizi) toplam += d;
  return toplam / dizi.length;
}
20.625

Fonksiyon bir ifade olarak bir değişkene atanmışsa, taşınan şey yalnızca değişken bildirimidir; fonksiyonun kendisi atama satırında oluşur. const ile atanmışsa ölü bölge kuralı geçerlidir:

try {
  console.log(ortalama([1, 2]));
} catch (hata) {
  console.log(hata.name + ": " + hata.message);
}
const ortalama = (dizi) => dizi.reduce((t, d) => t + d, 0) / dizi.length;
console.log(ortalama([1, 2]));
ReferenceError: Cannot access 'ortalama' before initialization
1.5

İki tanım biçiminin davranış farkı Fonksiyon Bildirimi ve İfadesi dersinde ayrıntılı olarak ele alınacak; burada yalnızca yukarı taşıma yönünden karşılaştırıldı.

Ölçüm Betiğine Uygulama

Kapsam kuralları, ölçüm betiğinin yapısını doğrudan belirler:

const ESIK = 20;

function esikRaporu(sicakliklar) {
  const asanlar = [];
  for (const deger of sicakliklar) {
    if (deger > ESIK) {
      const fark = deger - ESIK;
      asanlar.push(fark);
    }
  }
  return asanlar;
}

console.log(esikRaporu([21.5, 19.75, 23, 18.25]));
console.log(typeof asanlar);
[ 1.5, 3 ]
undefined

ESIK en dış kapsamdadır ve fonksiyon içinden sözcüksel kapsam zinciriyle görülür. asanlar fonksiyona, fark ise if bloğuna aittir; ikisi de dışarıdan erişilemez. Son satırın typeof ile yazılması zorunluydu — doğrudan okumak ReferenceError verirdi.

Yapı üç kuralı birlikte uygular: her ad ihtiyaç duyulan en dar kapsamda bildirilir, gölgeleme yapılmaz, bildirimler kullanımdan önce gelir. Üçüncüsü yukarı taşıma sayesinde zorunlu değildir, ama ölü bölge hatalarının tamamını ortadan kaldırır.

Özet

  • Adlar büyük–küçük harfe duyarlıdır; ayrılmış sözcükler ad olarak kullanılamaz ve bu ayrıştırma aşamasında yakalanır.
  • JavaScript sözcüksel kapsam kullanır: bir ad, kodun yazıldığı yerin çevreleyen kapsamlarında aranır; çağrı zinciri bu aramayı etkilemez.
  • İç kapsamda aynı adın bildirilmesi dış bağlamayı gölgeler; gölgeleme bildirim satırından önce de geçerlidir.
  • var kapsam başında undefined ile başlatılır; let ve const başlatılmaz ve bildirim satırına kadar zamansal ölü bölgede kalır.
  • Fonksiyon bildirimleri gövdeleriyle birlikte taşınır; bir değişkene atanmış fonksiyon ifadeleri taşınmaz.

Sonraki Adım

Dilin yapısı — kodun nasıl çalıştırıldığı, adların nasıl bildirildiği ve çözümlendiği — kuruldu. Sıra, bu adların bağlandığı değerlerde. Sonraki konu JavaScript’in değer modelini ele alacak: kaç ilkel tip vardır, sayı tipi hangi gösterimi kullanır ve neden tam sayılar için bir sınır taşır. İlk ders ilkel tipleri tek tek tanıtacak.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat