İçeriğe geç
academia.sh

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:

  1. Prototipi OlcumKaydi.prototype olan boş bir nesne yaratılır.
  2. Yapıcı işlev, this bu nesneye bağlanmış olarak çağrılır.
  3. Yapıcı işlev bir nesne döndürüyorsa o nesne, döndürmüyorsa ilk adımdaki nesne sonuç olur.
  4. 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ı: kalibreliKalibreliOlcum.prototypeOlcumKaydi.prototypeObject.prototypenull. 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, new ile üreteceği nesnelerin prototipi olacak nesnedir; fonksiyonun kendi prototipi değildir.
  • new, prototipi prototype nesnesine 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.create ile 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.
  • instanceof ve isPrototypeOf, 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat