İçeriğe geç
academia.sh

Ders 01 / 17

Nesne Prototipi

Her nesnenin taşıdığı prototip bağı, özelliklerin bu bağ üzerinden aranma sırası, kendi özellik ile devralınan özellik ayrımı ve zincirin sonu.

İçindekiler

JavaScript Temelleri kursu nesneyi anahtar–değer eşlemesi olarak tanıttı: özellik yazılır, okunur, silinir. Bu tanım günlük kullanım için yeterlidir ama bir soruyu yanıtsız bırakır. Boş bir nesne yaratıp toString özelliğini okuduğunuzda tanımsız değil, bir fonksiyon elde edersiniz. O fonksiyonu oraya kimse yazmadı.

Yanıt, dilin nesne modelinin merkezinde durur: her nesne, kendi özelliklerine ek olarak başka bir nesneye bir bağ taşır. Aradığınız özellik nesnenin kendisinde yoksa, arama bu bağ üzerinden devam eder. Bu ders o bağı kurar; kursun geri kalanı — sınıf sözdizimi de dahil — onun üzerine oturur.

Aynı Davranışın İki Kopyası

Kurs boyunca tek bir somut örnek üzerinde çalışacağız: bir sensörden okunan ölçüm kaydı. Kayıt üç veri taşır (sensör kimliği, ölçülen değer, ölçüm anı) ve en az bir davranış sunar (kendini okunabilir biçimde yazdırmak). En doğrudan kuruluş, her kaydı kendi başına bir nesne değişmezi olarak yazmaktır.

const kayitBir = {
  sensor: "S-01",
  deger: 21.4,
  zaman: 1000,
  bicimle() {
    return `${this.sensor}@${this.zaman}: ${this.deger}`;
  },
};

const kayitIki = {
  sensor: "S-02",
  deger: 19.8,
  zaman: 1060,
  bicimle() {
    return `${this.sensor}@${this.zaman}: ${this.deger}`;
  },
};

console.log(kayitBir.bicimle());
console.log(kayitIki.bicimle());
console.log(kayitBir.bicimle === kayitIki.bicimle);
S-01@1000: 21.4
S-02@1060: 19.8
false

Son satır sorunu görünür kılar. İki kayıt aynı davranışı sunar, ama bellekte iki ayrı fonksiyon nesnesi vardır; kimlik karşılaştırması bunu söyler. Bin ölçüm için bin kopya oluşur ve biçimlendirme kuralı değiştiğinde bin yerde değişmesi gerekir.

Programlama Temelleri kursundaki Nesneye Dayalı Programlamaya Giriş dersi bu soruna sınıf kavramıyla yanıt vermişti: davranış sınıfta bir kez yazılır, nesneler onu paylaşır. JavaScript’in yanıtı bir adım daha alttadır ve sınıf gerektirmez.

Prototip Bağı

Her nesne, yaratıldığı anda başka bir nesneye — ya da null değerine — bağlanır. Bu bağa prototip (prototype) denir. Bağ, nesnenin sıradan bir özelliği değildir; dilin belirtiminde [[Prototype]] adlı iç bir alan olarak geçer ve Object.getPrototypeOf işleviyle okunur.

Object.create bağı doğrudan kurar: verilen nesneyi prototip alan yeni bir nesne üretir.

const olcumDavranisi = {
  bicimle() {
    return `${this.sensor}@${this.zaman}: ${this.deger}`;
  },
  esikAstiMi(esik) {
    return this.deger > esik;
  },
};

function olcumKaydi(sensor, deger, zaman) {
  const kayit = Object.create(olcumDavranisi);
  kayit.sensor = sensor;
  kayit.deger = deger;
  kayit.zaman = zaman;
  return kayit;
}

const kayitBir = olcumKaydi("S-01", 21.4, 1000);
const kayitIki = olcumKaydi("S-02", 19.8, 1060);

console.log(kayitBir.bicimle());
console.log(kayitIki.esikAstiMi(20));
console.log(kayitBir.bicimle === kayitIki.bicimle);
console.log(Object.getPrototypeOf(kayitBir) === olcumDavranisi);
S-01@1000: 21.4
false
true
true

Üçüncü satır artık true verir: iki kayıt aynı fonksiyon nesnesini kullanır, çünkü fonksiyon kayıtlarda değil, ortak prototipte durur. Dördüncü satır bağın kendisini sınar — nesne modeliyle ilgili her iddia bu ders boyunca bu biçimde, doğrudan sınanabilir bir karşılaştırmayla gösterilecektir.

Veri her kayıtta ayrı, davranış tek yerde. Bu ayrım prototip modelinin tüm işlevidir: değişen kısım nesnede, değişmeyen kısım zincirde durur.

Özellik Arama Sırası

Bir özelliği okuduğunuzda çalışma zamanı şu sırayı izler:

  1. Nesnenin kendi özellikleri arasında ara. Bulunursa değeri döndür.
  2. Bulunamazsa prototipine geç, aynı aramayı orada yap.
  3. Zincir null değerine ulaşana kadar sürdür. Hiçbir yerde bulunmazsa undefined döndür.

Arama ilk bulduğunda durduğu için, nesnenin kendi özelliği aynı adlı prototip özelliğini gölgeler (shadowing). Programlama Temelleri kursundaki Kapsam ve Yaşam Süresi dersinde iç kapsamın dış kapsamdaki adı gölgelemesi anlatılmıştı; buradaki mekanizma aynı fikrin nesneler üzerindeki karşılığıdır.

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

const kayit = Object.create(olcumDavranisi);
kayit.sensor = "S-01";
kayit.deger = 21.4;
kayit.zaman = 1000;

console.log(kayit.bicimle());

kayit.bicimle = function () {
  return `[ham] ${this.deger}`;
};

console.log(kayit.bicimle());
console.log(Object.hasOwn(kayit, "bicimle"));
console.log(olcumDavranisi.bicimle.call(kayit));

delete kayit.bicimle;
console.log(kayit.bicimle());
S-01@1000: 21.4
[ham] 21.4
true
S-01@1000: 21.4
S-01@1000: 21.4

Üç gözlem önemlidir. Birincisi, atama prototipteki fonksiyonu değiştirmez; kayda ait yeni bir özellik yaratır. Yazma işlemi zincirde yukarı çıkmaz, her zaman nesnenin kendisine yazar. İkincisi, gölgelenen özellik yok olmaz; call ile hâlâ çağrılabilir (bu işlemin ayrıntısı Bağlam ve Kapanışlar konusundadır). Üçüncüsü, kendi özellik silindiğinde zincir yeniden görünür olur — silme, prototipteki tanımı geri getiriyormuş gibi görünür ama aslında yalnızca gölgeyi kaldırır.

Kendi Özellik ve Devralınan Özellik

Zincir üzerinden gelen özellikler çoğu okuma işleminde kendi özelliklerden ayırt edilmez; ayrımın önemli olduğu yerler ise sayma ve dizileştirme işlemleridir.

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

const kayit = Object.create(olcumDavranisi);
kayit.sensor = "S-01";
kayit.deger = 21.4;
kayit.zaman = 1000;

console.log("bicimle" in kayit);
console.log(Object.hasOwn(kayit, "bicimle"));
console.log(Object.keys(kayit).join(","));

const forInAnahtarlari = [];
for (const anahtar in kayit) {
  forInAnahtarlari.push(anahtar);
}
console.log(forInAnahtarlari.join(","));

console.log(JSON.stringify(kayit));
true
false
sensor,deger,zaman
sensor,deger,zaman,bicimle
{"sensor":"S-01","deger":21.4,"zaman":1000}

in işleci zincirin tamamına bakar, Object.hasOwn yalnızca nesnenin kendisine. Object.keys kendi özelliklerle sınırlıdır; for...in döngüsü ise zincirden gelen özellikleri de dolaşır ve bu nedenle nesne üzerinde gezinmek için güvenilir bir araç değildir. JSON.stringify de kendi özelliklerle çalışır — dizileştirme yalnızca veriyi alır, davranışı bırakır. Bu davranış, dersin sonundaki ayrımı önceden haber verir: bir nesnenin verisi kendisinde, kimliği zincirindedir.

Buradaki bicimle adının for...in çıktısında görünmesi, prototipteki özelliğin sayılabilir olmasından kaynaklanır. Sayılabilirliğin nasıl denetlendiği Özellik Tanımlayıcıları dersinin konusudur; sınıf sözdiziminin bu konudaki farkı da orada gösterilecektir.

Zincirin Sonu

Zincir sonsuz değildir. Nesne değişmeziyle yaratılan her nesnenin prototipi Object.prototype nesnesidir ve onun prototipi null değeridir. toString, valueOf, hasOwnProperty gibi adların her nesnede bulunmasının nedeni budur.

const olcumDavranisi = {
  bicimle() {
    return `${this.sensor}@${this.zaman}: ${this.deger}`;
  },
};
const kayit = Object.create(olcumDavranisi);

console.log(Object.getPrototypeOf(kayit) === olcumDavranisi);
console.log(Object.getPrototypeOf(olcumDavranisi) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));

console.log(typeof kayit.toString);
console.log(typeof kayit.bilinmeyenOzellik);

const sozluk = Object.create(null);
sozluk.sensor = "S-01";
console.log(Object.getPrototypeOf(sozluk));
console.log(typeof sozluk.toString);
console.log("toString" in sozluk);
true
true
null
function
undefined
null
undefined
false

Zincir üç halkadan oluşur: kayitolcumDavranisiObject.prototypenull. Var olmayan bir özellik istendiğinde çalışma zamanı bu üç durağı da gezer ve undefined döndürür; hata vermez. Arama maliyeti zincir uzunluğuyla artar, ancak her durakta yapılan iş bir sözlük aramasıdır — Veri Yapıları kursundaki Karma Tabloları dersinde incelenen ortalama sabit maliyetli erişimin ta kendisi.

Object.create(null) ile üretilen nesnenin zinciri hiç yoktur. Böyle bir nesne saf sözlük olarak davranır: toString gibi devralınan adlar bulunmadığı için, dışarıdan gelen anahtarların yerleşik adlarla çakışma riski ortadan kalkar. Anahtarları program dışından gelen eşlemelerde bu kuruluş, for...in ve in sonuçlarını da öngörülebilir kılar. Aynı ihtiyacın dildeki asıl karşılığı Map türüdür ve İleri Koleksiyonlar konusunda ele alınır.

Özet

  • Her nesne, kendi özelliklerine ek olarak bir prototip nesnesine bağ taşır; bağ Object.getPrototypeOf ile okunur ve doğrudan sınanabilir.
  • Özellik araması nesnenin kendisinden başlar, bulunamazsa zincirde yukarı ilerler ve null durağında undefined ile sonuçlanır.
  • Yazma işlemi zincire çıkmaz; her zaman nesnenin kendisine yazar ve aynı adlı prototip özelliğini gölgeler.
  • in zincirin tamamına, Object.hasOwn ve Object.keys yalnızca kendi özelliklere bakar; JSON.stringify de kendi özelliklerle çalışır.
  • Nesne değişmezleriyle kurulan zincir Object.prototype üzerinden null değerine ulaşır; Object.create(null) prototipsiz, saf sözlük davranışlı bir nesne üretir.

Sonraki Adım

Bu derste prototip elle kuruldu: önce ortak davranış nesnesi yazıldı, sonra her kayıt Object.create ile ona bağlandı. Bu kalıp çalışır ama bir tür tanımına benzemez — yeni bir kayıt türü türetmek, ortak alanları doldurmak ve iki kayıt türü arasında ortak davranışı paylaştırmak için tekrar eden bir düzen gerekir. Sonraki ders, nesneden nesneye kalıtımın yerleşik kalıplarını inceleyecek ve new işlecinin bu bağı sizin yerinize nasıl kurduğunu 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