İçeriğe geç
academia.sh

Ders 03 / 17

Sınıf Sözdizimi

Sınıf bildiriminin prototip zincirindeki birebir karşılığı, statik tarafın kalıtımı, yöntemlerin sayılabilirliği ve sözdizimin getirdiği gerçek farklar.

İçindekiler

Önceki ders iki katmanlı bir zinciri elle kurdu. Üç ayrı işlem gerekti: alt prototipin üst prototipe bağlanması, constructor özelliğinin onarılması ve üst yapıcının alt yapıcı içinden çağrılması. Üçünden biri unutulduğunda hata çalışma zamanında, çoğu kez uzak bir yerde ortaya çıkar.

Sınıf sözdizimi (class syntax), bu üç işlemi tek bir bildirimde toplar. Bu ders, sınıf bildiriminin her satırının hangi prototip işlemine karşılık geldiğini önceki dersteki sınamalarla gösterir, sonra sözdizimin gerçekten yeni olan yanlarını ayırır. Ana savı baştan söylemek gerekir: sınıf, prototip zincirinin sözdizimsel yüzüdür; altında farklı bir nesne modeli yoktur.

Aynı Zincir, Tek Bildirim

Ölçüm kaydı ve kalibreli ölçüm, bu kez sınıf bildirimiyle yazılır.

class OlcumKaydi {
  constructor(sensor, deger, zaman) {
    this.sensor = sensor;
    this.deger = deger;
    this.zaman = zaman;
  }

  bicimle() {
    return `${this.sensor}@${this.zaman}: ${this.deger}`;
  }

  okunanDeger() {
    return this.deger;
  }
}

class KalibreliOlcum extends OlcumKaydi {
  constructor(sensor, deger, zaman, kayma) {
    super(sensor, deger, zaman);
    this.kayma = kayma;
  }

  okunanDeger() {
    return super.okunanDeger() + this.kayma;
  }
}

const kalibreli = new KalibreliOlcum("S-03", 21.4, 1120, -0.4);

console.log(kalibreli.bicimle());
console.log(kalibreli.okunanDeger());
console.log(typeof OlcumKaydi);
console.log(Object.getPrototypeOf(kalibreli) === KalibreliOlcum.prototype);
console.log(Object.getPrototypeOf(KalibreliOlcum.prototype) === OlcumKaydi.prototype);
console.log(kalibreli.constructor === KalibreliOlcum);
console.log(Object.keys(kalibreli).join(","));
S-03@1120: 21.4
21
function
true
true
true
sensor,deger,zaman,kayma

Çıktının ilk iki satırı önceki dersin çıktısıyla aynıdır. Kalan satırlar sözdizimin kurduğu yapıyı açığa çıkarır:

  • typeof OlcumKaydi sonucu "function" — sınıf bildirimi yeni bir değer türü üretmez, bir fonksiyon üretir.
  • Örneğin prototipi KalibreliOlcum.prototype nesnesidir; extends, bu nesnenin prototipini OlcumKaydi.prototype yapar.
  • constructor özelliği doğru fonksiyonu gösterir; elle onarım gerekmez.
  • Örneğin kendi özellikleri yalnızca dört veri alanıdır. Yöntemler örnekte değil, prototype nesnesinde durur.

super.okunanDeger() çağrısı da zincirle açıklanır: aramayı this üzerinden değil, bir üst prototipten başlatır. Önceki derste bu iş OlcumKaydi.prototype.okunanDeger.call(this) biçiminde elle yazılmıştı; super aynı işin adıdır.

Statik Taraf

Sınıf bildirimi static sözcüğüyle işaretlenen üyeleri örneklere değil, sınıf fonksiyonunun kendisine yazar. Burada elle kurulan kalıptan gerçek bir fark ortaya çıkar: extends yalnızca prototype nesnelerini değil, sınıf fonksiyonlarını da birbirine bağlar.

class OlcumKaydi {
  static olcuBirimi = "santigrat";

  static metinden(satir) {
    const [sensor, deger, zaman] = satir.split(";");
    return new this(sensor, Number(deger), Number(zaman));
  }

  constructor(sensor, deger, zaman) {
    this.sensor = sensor;
    this.deger = deger;
    this.zaman = zaman;
  }

  bicimle() {
    return `${this.sensor}@${this.zaman}: ${this.deger}`;
  }
}

class KalibreliOlcum extends OlcumKaydi {}

const kayit = OlcumKaydi.metinden("S-01;21.4;1000");
const alt = KalibreliOlcum.metinden("S-03;22.1;1120");

console.log(kayit.bicimle());
console.log(alt.bicimle());
console.log(alt instanceof KalibreliOlcum);
console.log(KalibreliOlcum.olcuBirimi);
console.log(Object.getPrototypeOf(KalibreliOlcum) === OlcumKaydi);
console.log(Object.hasOwn(KalibreliOlcum, "olcuBirimi"));
S-01@1000: 21.4
S-03@1120: 22.1
true
santigrat
true
false

Son iki satır olayı özetler: KalibreliOlcum sınıfı olcuBirimi özelliğini kendisi taşımaz, prototipi olan OlcumKaydi üzerinden devralır. Yani sınıf bildirimi iki zincir kurar — biri örnekler için prototype nesneleri arasında, biri sınıf fonksiyonları arasında. Önceki dersteki elle kuruluşta ikincisi hiç kurulmuyordu.

metinden yönteminin içindeki new this(...) ifadesi bu ikinci zincirin sonucudur: statik yöntem hangi sınıf üzerinden çağrıldıysa this o sınıfa bağlanır, bu yüzden alt sınıftan çağrıldığında alt sınıfın örneği üretilir.

Yöntemlerin Sayılabilirliği

İlk derste for...in döngüsünün prototipten gelen bicimle adını da listelediği görülmüştü. Sınıf sözdizimiyle tanımlanan yöntemlerde bu olmaz.

class OlcumKaydi {
  constructor(sensor, deger) {
    this.sensor = sensor;
    this.deger = deger;
  }
  bicimle() {
    return `${this.sensor}: ${this.deger}`;
  }
}

function EskiOlcumKaydi(sensor, deger) {
  this.sensor = sensor;
  this.deger = deger;
}
EskiOlcumKaydi.prototype.bicimle = function () {
  return `${this.sensor}: ${this.deger}`;
};

const yeni = new OlcumKaydi("S-01", 21.4);
const eski = new EskiOlcumKaydi("S-01", 21.4);

console.log(Object.keys(OlcumKaydi.prototype).join(",") || "(bos)");
console.log(Object.keys(EskiOlcumKaydi.prototype).join(",") || "(bos)");
console.log(Object.getOwnPropertyNames(OlcumKaydi.prototype).join(","));

const yeniAnahtarlar = [];
for (const anahtar in yeni) yeniAnahtarlar.push(anahtar);
const eskiAnahtarlar = [];
for (const anahtar in eski) eskiAnahtarlar.push(anahtar);

console.log(yeniAnahtarlar.join(","));
console.log(eskiAnahtarlar.join(","));
(bos)
bicimle
constructor,bicimle
sensor,deger
sensor,deger,bicimle

Sınıf yöntemleri prototipte vardır — üçüncü satır bunu gösterir — ama sayılamaz olarak tanımlanmıştır; bu yüzden Object.keys ve for...in onları atlar. Elle yazılan atama ise sayılabilir bir özellik üretir ve döngüye sızar. Sayılabilirliğin ne olduğu ve elle nasıl denetlendiği sonraki dersin konusudur.

Sözdizimin Getirdiği Gerçek Farklar

Sınıf bildirimi yalnızca kısaltma değildir; birkaç davranışı da değiştirir. En görünür ikisi doğrudan sınanabilir.

function eskiKayit(sensor) {
  this.sensor = sensor;
}
const bildirimdenOnce = new eskiKayit("S-01");
console.log(bildirimdenOnce.sensor);

try {
  new OlcumKaydi("S-01");
} catch (hata) {
  console.log(hata.constructor.name);
}

class OlcumKaydi {
  constructor(sensor) {
    this.sensor = sensor;
  }
}

console.log(new OlcumKaydi("S-01").sensor);

try {
  OlcumKaydi("S-01");
} catch (hata) {
  console.log(hata.constructor.name);
}
S-01
ReferenceError
S-01
TypeError

Birinci fark yukarı taşımadır. Fonksiyon bildirimi tanımından önce çağrılabilir; sınıf bildirimi çağrılamaz, çünkü adı bildirime kadar geçici ölü bölgededir. JavaScript Temelleri kursundaki Adlandırma ve Kapsam Kuralları dersinde let ve const için anlatılan davranışın aynısıdır.

İkinci fark çağrı biçimidir. Yapıcı işlev new olmadan çağrılabilir ve bu durumda this beklenmedik bir değere bağlanır; sınıf yapıcısı new olmadan çağrıldığında hata verir. Üçüncü fark çıktıda görünmez: sınıf gövdesindeki tüm kod katı kipte (strict mode) çalışır, bildirilmemiş değişkene atama gibi sessiz hatalar burada sessiz kalmaz.

Özel Alanlar

Elle kurulan kalıpta bir nesnenin iç durumunu gizlemenin dil düzeyinde bir yolu yoktu; adlandırma anlaşmaları (baştaki alt çizgi) yalnızca niyet bildiriyordu. Sınıf sözdizimi, adı diyez imiyle başlayan özel alanları (private fields) getirir. Bunlar sıradan özellik değildir — prototip zincirinde de, nesnenin özellik listesinde de görünmezler.

class OlcumKaydi {
  #kayma;

  constructor(sensor, deger, kayma = 0) {
    this.sensor = sensor;
    this.deger = deger;
    this.#kayma = kayma;
  }

  get okunanDeger() {
    return this.deger + this.#kayma;
  }

  kaymaTasiyorMu() {
    return this.#kayma !== 0;
  }

  static kaymaGorunur(nesne) {
    return #kayma in nesne;
  }
}

const kayit = new OlcumKaydi("S-01", 21.4, -0.4);

console.log(kayit.okunanDeger);
console.log(kayit.kaymaTasiyorMu());
console.log(Object.keys(kayit).join(","));
console.log(JSON.stringify(kayit));
console.log(Object.getOwnPropertyNames(kayit).join(","));
console.log(OlcumKaydi.kaymaGorunur(kayit));
console.log(OlcumKaydi.kaymaGorunur({ sensor: "S-02", deger: 19.8 }));
console.log(Object.getOwnPropertyNames(OlcumKaydi.prototype).join(","));
21
true
sensor,deger
{"sensor":"S-01","deger":21.4}
sensor,deger
true
false
constructor,okunanDeger,kaymaTasiyorMu

Özel alan hiçbir listeleme işleminde görünmez ve dizileştirmeye girmez; sınıf gövdesi dışından erişmeyi denemek çalışma zamanı hatası bile üretmez, kod hiç ayrıştırılamaz. Erişim, yalnızca alanı bildiren sınıfın gövdesinden mümkündür. #kayma in nesne biçimi, bir nesnenin bu alanı taşıyıp taşımadığını hata yükseltmeden sorar.

Son satır bir ayrıntıyı daha gösterir: okunanDeger bir erişimci (accessor) olarak tanımlandığı hâlde prototipte durur. Yani kayit.okunanDeger ifadesi bir özellik okuma gibi görünür ama zincirde bulunan bir fonksiyonu çalıştırır. Erişimcilerin nasıl tanımlandığı sonraki dersin ikinci konusudur.

Kapsülleme için dilin sunduğu ikinci yol kapanışlardır ve gizlemeyi bambaşka bir mekanizmayla, prototipe hiç dokunmadan sağlar; ikisinin karşılaştırması Bağlam ve Kapanışlar konusundaki Kapanışlar dersinde yapılacaktır.

Özet

  • Sınıf bildirimi bir fonksiyon üretir; örnekler prototype nesnesine bağlanır ve yöntemler orada durur.
  • extends iki zincir kurar: örnek prototipleri arasında ve sınıf fonksiyonları arasında; ikincisi statik üyelerin devralınmasını sağlar.
  • super, aramayı bir üst prototipten başlatan ifadedir; elle yazılan call çağrısının karşılığıdır.
  • Sınıf yöntemleri prototipte sayılamaz olarak tanımlanır, bu yüzden for...in ve Object.keys sonuçlarına girmez.
  • Sözdizimin gerçek farkları şunlardır: geçici ölü bölge, new zorunluluğu, katı kip ve özel alanlar.
  • Özel alanlar özellik değildir; listelenmez, dizileştirilmez ve yalnızca bildiren sınıf gövdesinden erişilir.

Sonraki Adım

Bu derste iki kez sayılabilirliğe çarpıldı: sınıf yöntemleri for...in çıktısına girmiyor, elle yazılan atamalar giriyordu. Bir de erişimci ortaya çıktı — okuma gibi görünüp fonksiyon çalıştıran özellik. Her ikisi de özelliklerin yalnızca bir değerden ibaret olmadığını, yanlarında davranışlarını belirleyen bayraklar taşıdığını gösterir. Sonraki ders bu bayrakları — yazılabilirlik, sayılabilirlik, yapılandırılabilirlik — ve erişimcileri doğrudan ele alacak.

İ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