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 OlcumKaydisonucu"function"— sınıf bildirimi yeni bir değer türü üretmez, bir fonksiyon üretir.- Örneğin prototipi
KalibreliOlcum.prototypenesnesidir;extends, bu nesnenin prototipiniOlcumKaydi.prototypeyapar. 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,
prototypenesnesinde 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
prototypenesnesine bağlanır ve yöntemler orada durur. extendsiki 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ılancallç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...inveObject.keyssonuçlarına girmez. - Sözdizimin gerçek farkları şunlardır: geçici ölü bölge,
newzorunluluğ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.