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:
- Nesnenin kendi özellikleri arasında ara. Bulunursa değeri döndür.
- Bulunamazsa prototipine geç, aynı aramayı orada yap.
- Zincir
nulldeğerine ulaşana kadar sürdür. Hiçbir yerde bulunmazsaundefineddö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: kayit → olcumDavranisi → Object.prototype → null.
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.getPrototypeOfile okunur ve doğrudan sınanabilir. - Özellik araması nesnenin kendisinden başlar, bulunamazsa zincirde yukarı ilerler ve
nulldurağındaundefinedile sonuçlanır. - Yazma işlemi zincire çıkmaz; her zaman nesnenin kendisine yazar ve aynı adlı prototip özelliğini gölgeler.
inzincirin tamamına,Object.hasOwnveObject.keysyalnızca kendi özelliklere bakar;JSON.stringifyde kendi özelliklerle çalışır.- Nesne değişmezleriyle kurulan zincir
Object.prototypeüzerindennulldeğ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.