Ders 02 / 17
Prototipsel Kalıtım
Yapıcı işlevler ve new işlecinin kurduğu bağ, prototip zincirini iki katmana çıkarmak, tür sorgulama işlemleri ve bağın çalışma zamanında değişmesi.
İçindekiler
Önceki ders prototip bağını elle kurdu: ortak davranış bir nesnede toplandı, her kayıt
Object.create ile ona bağlandı, alanlar tek tek dolduruldu. Kalıp işler ama iki
eksiği vardır. Alanların doldurulması ile davranışın bağlanması ayrı yerlerde durur, ve
bir kayıt türünden başka bir kayıt türü türetmenin yerleşik bir düzeni yoktur.
Dil, her ikisi için de yerleşik bir yol sunar. Bu ders o yolu — yapıcı işlev ve new
işlecini — açar, sonra new işlecinin yaptığı işi elle yazarak bunun bir sihir değil,
önceki derste gördüğünüz üç adımın kısaltması olduğunu gösterir.
Yapıcı İşlev
JavaScript’te her fonksiyon, prototype adlı sıradan bir özellik taşır. Bu özellik
fonksiyonun kendi prototipi değildir; new ile çağrıldığında üreteceği nesnelerin
prototipi olacak nesnedir. Ad benzerliği bu ayrımı gizlediği için, iki kavramı
karıştırmamak gerekir.
new ile çağrılmak üzere yazılan fonksiyona yapıcı işlev (constructor function)
denir. Alanları this üzerinden doldurur, davranışları ise prototype nesnesine
yazılır.
function OlcumKaydi(sensor, deger, zaman) { this.sensor = sensor; this.deger = deger; this.zaman = zaman; } OlcumKaydi.prototype.bicimle = function () { return `${this.sensor}@${this.zaman}: ${this.deger}`; }; OlcumKaydi.prototype.esikAstiMi = function (esik) { return this.deger > esik; }; const kayitBir = new OlcumKaydi("S-01", 21.4, 1000); const kayitIki = new OlcumKaydi("S-02", 19.8, 1060); console.log(kayitBir.bicimle()); console.log(Object.getPrototypeOf(kayitBir) === OlcumKaydi.prototype); console.log(kayitBir.bicimle === kayitIki.bicimle); console.log(Object.keys(kayitBir).join(",")); console.log(kayitBir.constructor === OlcumKaydi); console.log(Object.hasOwn(OlcumKaydi.prototype, "constructor"));
S-01@1000: 21.4 true true sensor,deger,zaman true true
Sonuç önceki dersteki kuruluşla aynıdır: veri her kayıtta ayrı, davranış tek yerde. Dördüncü satır bunu doğrular — kaydın kendi özellikleri yalnızca üç veri alanıdır.
Beşinci ve altıncı satırlar bir ayrıntıyı açığa çıkarır. Her fonksiyonun prototype
nesnesi, yaratıldığı anda constructor adlı bir özellik taşır ve bu özellik fonksiyonun
kendisini gösterir. kayitBir.constructor ifadesi kayıtta bulunmaz; zincirden,
OlcumKaydi.prototype üzerinden gelir. Yani constructor bir nesnenin “gerçek türü”
değil, zincirinde rastlanan sıradan bir özelliktir — sonraki bölümde bu ayrımın bedeli
görülecektir.
new İşlecinin Yaptığı İş
new OlcumKaydi(...) ifadesi dört adıma ayrılır:
- Prototipi
OlcumKaydi.prototypeolan boş bir nesne yaratılır. - Yapıcı işlev,
thisbu nesneye bağlanmış olarak çağrılır. - Yapıcı işlev bir nesne döndürüyorsa o nesne, döndürmüyorsa ilk adımdaki nesne sonuç olur.
- Sonuç ifadenin değeri olur.
Bu adımların hepsi sıradan işlemlerdir; hiçbirinde dile özel bir yetki yoktur. Aynı işi yapan bir fonksiyon yazılabilir.
function OlcumKaydi(sensor, deger, zaman) { this.sensor = sensor; this.deger = deger; this.zaman = zaman; } OlcumKaydi.prototype.bicimle = function () { return `${this.sensor}@${this.zaman}: ${this.deger}`; }; function yeniOrnek(YapiciIslev, ...argumanlar) { const nesne = Object.create(YapiciIslev.prototype); const sonuc = YapiciIslev.apply(nesne, argumanlar); return typeof sonuc === "object" && sonuc !== null ? sonuc : nesne; } const elle = yeniOrnek(OlcumKaydi, "S-01", 21.4, 1000); const islecle = new OlcumKaydi("S-01", 21.4, 1000); console.log(elle.bicimle()); console.log(Object.getPrototypeOf(elle) === OlcumKaydi.prototype); console.log(JSON.stringify(elle) === JSON.stringify(islecle)); console.log(elle instanceof OlcumKaydi);
S-01@1000: 21.4 true true true
yeniOrnek işlevi new işlecinin yerine geçer ve ayırt edilemeyen bir sonuç üretir.
apply çağrısı this bağlamasını açıkça kurar; bu işlemin kuralları Bağlam ve
Kapanışlar konusunun ilk iki dersinde ele alınacaktır. Buradaki asıl gözlem şudur:
new, prototip modelinin üzerine konmuş bir kısaltmadır, ayrı bir mekanizma değil.
Yazdığımız yeniOrnek işlevi new işlecinin tamamını karşılamaz; sınıf sözdizimiyle
tanımlanmış yapıcılar new olmadan çağrılamaz ve new.target gibi ayrıntılar bu
kısaltmanın dışında kalır. Model açısından fark eden kısım yukarıdaki üç satırdır.
Zinciri Uzatmak
Ölçüm kayıtlarının bir alt türünü ele alalım: sensörün sistematik sapmasını taşıyan kalibreli ölçüm. Alanları ölçüm kaydının alanlarını kapsar, üzerine bir kayma değeri ekler; okunan değeri hesaplama biçimi farklıdır, biçimlendirme biçimi aynıdır.
Zinciri iki katmana çıkarmak için iki iş gerekir: alt türün prototipi üst türün prototipine bağlanmalı, ve alt yapıcı çalışırken üst yapıcının alan doldurma işi de yapılmalıdır.
function OlcumKaydi(sensor, deger, zaman) { this.sensor = sensor; this.deger = deger; this.zaman = zaman; } OlcumKaydi.prototype.bicimle = function () { return `${this.sensor}@${this.zaman}: ${this.deger}`; }; OlcumKaydi.prototype.okunanDeger = function () { return this.deger; }; function KalibreliOlcum(sensor, deger, zaman, kayma) { OlcumKaydi.call(this, sensor, deger, zaman); this.kayma = kayma; } KalibreliOlcum.prototype = Object.create(OlcumKaydi.prototype); KalibreliOlcum.prototype.constructor = KalibreliOlcum; KalibreliOlcum.prototype.okunanDeger = function () { return this.deger + this.kayma; }; const kalibreli = new KalibreliOlcum("S-03", 21.4, 1120, -0.4); console.log(kalibreli.bicimle()); console.log(kalibreli.okunanDeger()); console.log(OlcumKaydi.prototype.okunanDeger.call(kalibreli)); console.log(Object.getPrototypeOf(kalibreli) === KalibreliOlcum.prototype); console.log(Object.getPrototypeOf(KalibreliOlcum.prototype) === OlcumKaydi.prototype); console.log(kalibreli.constructor === KalibreliOlcum);
S-03@1120: 21.4 21 21.4 true true true
Zincir dört halkaya çıktı: kalibreli → KalibreliOlcum.prototype →
OlcumKaydi.prototype → Object.prototype → null. bicimle ikinci halkada
bulunmaz, üçüncüde bulunur; okunanDeger ikinci halkada bulunduğu için üçüncüdeki
tanımı gölgeler. Programlama Temelleri kursundaki Nesneye Dayalı Programlamaya Giriş
dersinde tanıtılan çok biçimlilik, burada tam olarak bu gölgelemedir: aynı ad, zincirin
farklı katmanlarında farklı gerçekleştirimlere karşılık gelir ve hangisinin çalışacağına
arama sırası karar verir.
Üç satır dikkat ister. OlcumKaydi.call(this, ...) üst yapıcının alan doldurma işini
yeni nesne üzerinde çalıştırır. Object.create(OlcumKaydi.prototype) ile kurulan bağ,
KalibreliOlcum.prototype = OlcumKaydi.prototype biçiminde kurulsaydı iki tür aynı
prototip nesnesini paylaşırdı ve alt türe eklenen her davranış üst türe de sızardı.
constructor özelliğinin elle geri yazılması ise ilk bölümdeki gözlemin sonucudur:
prototype nesnesi değiştirildiğinde constructor ile birlikte kaybolur, yeniden
yazılmazsa kalibreli.constructor zincirden OlcumKaydi değerini bulur.
Zincirde Tür Sorgulama
instanceof işleci, sağındaki fonksiyonun prototype nesnesinin, solundaki nesnenin
zincirinde bulunup bulunmadığını sorar. Sınıf üyeliği gibi görünen bu sorgu, aslında bir
zincir taramasıdır.
function OlcumKaydi(sensor, deger) { this.sensor = sensor; this.deger = deger; } function KalibreliOlcum(sensor, deger, kayma) { OlcumKaydi.call(this, sensor, deger); this.kayma = kayma; } KalibreliOlcum.prototype = Object.create(OlcumKaydi.prototype); KalibreliOlcum.prototype.constructor = KalibreliOlcum; const kalibreli = new KalibreliOlcum("S-03", 21.4, -0.4); const yalin = new OlcumKaydi("S-01", 21.4); console.log(kalibreli instanceof KalibreliOlcum); console.log(kalibreli instanceof OlcumKaydi); console.log(kalibreli instanceof Object); console.log(yalin instanceof KalibreliOlcum); console.log(OlcumKaydi.prototype.isPrototypeOf(kalibreli)); console.log(KalibreliOlcum.prototype.isPrototypeOf(yalin)); let durak = kalibreli; const zincir = []; while (durak !== null) { durak = Object.getPrototypeOf(durak); zincir.push(durak === null ? "null" : durak.constructor.name); } console.log(zincir.join(" -> "));
true true true false true false KalibreliOlcum -> OlcumKaydi -> Object -> null
Son blok zinciri baştan sona yürüyerek yazdırır ve instanceof sonuçlarının nereden
geldiğini görünür kılar. Kalibreli ölçümün zincirinde her üç prototype nesnesi de
bulunduğu için üç sorgu da olumlu yanıtlanır; yalın kayıt alt türün prototipini
zincirinde taşımadığı için dördüncü sorgu olumsuzdur. isPrototypeOf aynı soruyu ters
yönden sorar ve fonksiyona değil doğrudan prototip nesnesine bakar.
Bağın Canlılığı
Prototip bağı bir kopya değil, canlı bir başvurudur. Nesne yaratıldıktan sonra prototipe eklenen bir özellik, var olan tüm nesnelerde hemen görünür.
function OlcumKaydi(sensor, deger) { this.sensor = sensor; this.deger = deger; } OlcumKaydi.prototype.bicimle = function () { return `${this.sensor}: ${this.deger}`; }; const kayit = new OlcumKaydi("S-01", 21.4); console.log(kayit.bicimle()); console.log(typeof kayit.ozetle); OlcumKaydi.prototype.ozetle = function () { return `${this.sensor} kaydi`; }; console.log(typeof kayit.ozetle); console.log(kayit.ozetle()); const digerDavranis = { bicimle() { return `[${this.sensor}] ${this.deger}`; }, }; Object.setPrototypeOf(kayit, digerDavranis); console.log(kayit.bicimle()); console.log(kayit instanceof OlcumKaydi); console.log(typeof kayit.ozetle);
S-01: 21.4 undefined function S-01 kaydi [S-01] 21.4 false undefined
ozetle kaydın yaratılmasından sonra eklenmiştir ve hiçbir yeniden yaratma olmadan
erişilebilir olur; arama her okumada baştan yapıldığı için bağ canlıdır.
Object.setPrototypeOf var olan bir nesnenin bağını değiştirir. Sonuçlar geniştir:
kayıt artık üst türün davranışlarını görmez, instanceof sonucu tersine döner, ve
çalışma zamanları nesne biçimlerine göre eniyileme yaptığı için erişim başarımı düşer.
Bu nedenle bağ, nesnenin yaratıldığı anda kurulur ve sonradan değiştirilmez;
Object.create ya da new bunun için vardır.
Özet
- Her fonksiyonun
prototypeözelliği,newile üreteceği nesnelerin prototipi olacak nesnedir; fonksiyonun kendi prototipi değildir. new, prototipiprototypenesnesine bağlı boş bir nesne yaratır, yapıcıyı bu nesne üzerinde çalıştırır ve sonucu döndürür; aynı işObject.createile elle yazılabilir.- Zinciri uzatmak iki işten oluşur: alt prototipi üst prototipe bağlamak ve üst yapıcıyı alt yapıcı içinden çağırmak.
- Alt katmandaki aynı adlı tanım üsttekini gölgeler; çok biçimlilik bu arama sırasının sonucudur.
instanceofveisPrototypeOf, tür üyeliğini değil, bir prototip nesnesinin zincirde bulunup bulunmadığını sorar.- Bağ canlıdır: prototipe sonradan eklenen özellikler var olan nesnelerde görünür.
Sonraki Adım
Bu derste zincir elle kuruldu ve kuruluşun her adımı görünür oldu: prototype ataması,
constructor onarımı, üst yapıcı çağrısı. Üç adımın da unutulmaya açık olması, dile
bunları tek bir bildirimde toplayan bir sözdizim eklenmesinin gerekçesidir. Sonraki ders
sınıf sözdizimini tanıtacak ve her satırının bu derste elle yazdığınız hangi işleme
karşılık geldiğini, aynı sınamalarla gösterecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.