Prototipal Kalıtım
Neden Bu Konu Önemli?
Java, C++ veya Python'dan geliyorsan, "class" ve "kalıtım" kavramlarına alışkınsındır. Bu dillerde sınıflar, nesnelerin şablonudur — bir sınıf tanımlar, ondan nesneler üretirsin. Ama JavaScript temelde farklı çalışır. JavaScript'te sınıflar yoktu (ES6'ya kadar) ve hâlâ gerçek sınıflar yok. ES6'daki class keyword'ü, aslında prototipal kalıtımın üzerine yerleştirilmiş bir "sözdizimsel şeker"dir.
Peki prototipal kalıtım ne demek? Bunu şöyle düşün: bir aile şirketi. Baba bir iş kurmuş, bazı beceriler edinmiş. Oğlu şirkete katıldığında, kendi becerileri olmayan konularda babasına soruyor. Baba da bilmiyorsa, dedeye soruluyor. Bu "yukarı doğru sormak" zinciri, JavaScript'teki prototype chain (prototip zinciri) ile birebir örtüşüyor.
Bu derste JavaScript'in kalıtım modelinin temellerini — prototype chain, __proto__, Object.create, constructor fonksiyonlar ve new keyword'ünün arka planını — öğreneceğiz. Bu bilgi olmadan ES6 class'larını gerçek anlamda anlayamazsın.
Her Nesnenin Bir Prototipi Var
JavaScript'te (neredeyse) her nesnenin bir prototipi vardır. Prototip, o nesnenin "üst nesne"sidir — bir özellik veya metot nesnede bulunamazsa, prototipinde aranır.
// Basit bir nesne oluştur
const kedi = {
tur: "kedi",
miyavla() {
return "Miyav!";
},
};
// kedi nesnesinin prototipi nedir?
console.log(Object.getPrototypeOf(kedi));
// Object.prototype — tüm nesnelerin en üst prototipi
// Bu yüzden her nesne toString(), hasOwnProperty() gibi
// metotlara sahiptir — bunlar Object.prototype'tan gelir
console.log(kedi.toString()); // "[object Object]"
console.log(kedi.hasOwnProperty("tur")); // true
// kedi'nin kendisinde toString yok — prototipinden bulunuyorPrototype Chain (Prototip Zinciri)
Bir özellik arandığında JavaScript şu sırayla bakar:
1. Nesnenin kendisi → bulunursa döner
2. Nesnenin prototipi → bulunursa döner
3. Prototipinin prototipi → bulunursa döner
4. ... (zincir yukarı doğru devam eder)
5. Object.prototype → son durak
6. null → zincirin sonu, undefined dönerconst hayvan = {
canli: true,
nefesAl() {
return "Nefes alıyor...";
},
};
const kedi = Object.create(hayvan); // hayvan'ı prototip olarak kullan
kedi.miyavla = function () {
return "Miyav!";
};
const tekir = Object.create(kedi); // kedi'yi prototip olarak kullan
tekir.isim = "Pamuk";
// Prototype chain: tekir → kedi → hayvan → Object.prototype → null
console.log(tekir.isim); // "Pamuk" — tekir'in kendisinde
console.log(tekir.miyavla()); // "Miyav!" — kedi'den geliyor
console.log(tekir.nefesAl()); // "Nefes alıyor..." — hayvan'dan geliyor
console.log(tekir.canli); // true — hayvan'dan geliyor
console.log(tekir.toString()); // "[object Object]" — Object.prototype'tan
console.log(tekir.ucabilir); // undefined — zincirde hiç bulunamadı__proto__ vs prototype
Bu iki terim sürekli karıştırılır. Farkı net anlayalım:
// __proto__ → HER nesnenin sahip olduğu, prototipine işaret eden referans
// prototype → Sadece FONKSİYONLARIN sahip olduğu bir özellik
// (new ile oluşturulacak nesnelerin prototipi olacak nesne)
function Kisi(ad) {
this.ad = ad;
}
Kisi.prototype.selamla = function () {
return `Merhaba, ben ${this.ad}`;
};
const ahmet = new Kisi("Ahmet");
// ahmet.__proto__ === Kisi.prototype → TRUE
console.log(Object.getPrototypeOf(ahmet) === Kisi.prototype); // true
// Kisi.prototype.__proto__ === Object.prototype → TRUE
console.log(Object.getPrototypeOf(Kisi.prototype) === Object.prototype); // true
// Zincir: ahmet → Kisi.prototype → Object.prototype → null⚠️ Dikkat:
__proto__özelliği standart dışıdır (legacy). Prototipe erişmek içinObject.getPrototypeOf(), ayarlamak içinObject.setPrototypeOf()veyaObject.create()kullan.
Object.create — Prototip Belirleme
Object.create(proto) belirtilen prototipi kullanarak yeni bir nesne oluşturur. Bu, prototipal kalıtımın en saf halidir.
// Object.create ile kalıtım zinciri
const arac = {
tur: "araç",
calistir() {
return `${this.marka} çalıştırıldı`;
},
durdur() {
return `${this.marka} durduruldu`;
},
};
// araba, arac'ı prototip olarak kullanır
const araba = Object.create(arac);
araba.tur = "araba";
araba.tekerlekSayisi = 4;
araba.kornaCal = function () {
return "Düüüt!";
};
// bmw, araba'yı prototip olarak kullanır
const bmw = Object.create(araba);
bmw.marka = "BMW";
bmw.model = "320i";
console.log(bmw.calistir()); // "BMW çalıştırıldı" — arac'tan
console.log(bmw.kornaCal()); // "Düüüt!" — araba'dan
console.log(bmw.tekerlekSayisi); // 4 — araba'dan
console.log(bmw.marka); // "BMW" — bmw'nin kendisindeObject.create ile Property Descriptors
// İkinci parametre ile özellik tanımlayıcıları
const kullanici = Object.create(
// Prototip
{
selamla() {
return `Merhaba, ${this.ad}`;
},
},
// Özellik tanımlayıcıları
{
ad: {
value: "Ahmet",
writable: true,
enumerable: true,
configurable: true,
},
id: {
value: 1,
writable: false, // Değiştirilemez
enumerable: true,
configurable: false, // Silinemez, yeniden tanımlanamaz
},
}
);
console.log(kullanici.selamla()); // "Merhaba, Ahmet"
kullanici.ad = "Mehmet"; // ✅ Değiştirilebilir
// kullanici.id = 2; // ❌ Strict mode'da TypeErrorObject.create(null) — Saf Sözlük
// Prototipi olmayan nesne — güvenli sözlük
const sozluk = Object.create(null);
// Bu nesnenin prototipi NULL — Object.prototype bile yok
// toString, hasOwnProperty, constructor — hiçbiri yok
sozluk["toString"] = "bir değer";
// Normal nesnede toString metotla çakışırdı — burada sorun yok!
console.log(sozluk.toString); // "bir değer" — metot değil, string!
// Normal nesnede: console.log({}.toString) → function toString()
// JSON parse sonuçları için güvenli:
// Kötü niyetli JSON'da "__proto__" key'i bile sorun çıkarmaz
sozluk["__proto__"] = "tehlikeli";
console.log(Object.getPrototypeOf(sozluk)); // null — hâlâ güvenliConstructor Fonksiyonlar ve new Keyword'ü
ES6 class'larından önce, JavaScript'te "sınıf" oluşturmanın yolu constructor (yapıcı) fonksiyonlardı.
// Constructor fonksiyon — büyük harfle başlar (convention)
function Kullanici(ad, email) {
// `new` ile çağrıldığında `this` yeni nesneyi gösterir
this.ad = ad;
this.email = email;
this.olusturmaTarihi = new Date();
}
// Metotları prototype'a ekle — tüm instance'lar paylaşır
Kullanici.prototype.selamla = function () {
return `Merhaba, ben ${this.ad}`;
};
Kullanici.prototype.bilgiGoster = function () {
return `${this.ad} (${this.email})`;
};
// new keyword'ü ile nesne oluştur
const ahmet = new Kullanici("Ahmet", "ahmet@test.com");
const ayse = new Kullanici("Ayşe", "ayse@test.com");
console.log(ahmet.selamla()); // "Merhaba, ben Ahmet"
console.log(ayse.bilgiGoster()); // "Ayşe (ayse@test.com)"
// Her iki instance da aynı prototype'ı paylaşır
console.log(ahmet.selamla === ayse.selamla); // true — aynı fonksiyon!new Keyword'ü Arkada Ne Yapıyor?
new Kullanici("Ahmet", "ahmet@test.com") çağrıldığında JavaScript arkada 4 adım yapar:
// `new` operatörünün simülasyonu
function yeniOlustur(Constructor, ...args) {
// 1. Boş bir nesne oluştur
const nesne = {};
// 2. Nesnenin prototipini Constructor.prototype'a bağla
Object.setPrototypeOf(nesne, Constructor.prototype);
// 3. Constructor'ı bu nesne context'inde çalıştır
const sonuc = Constructor.apply(nesne, args);
// 4. Constructor nesne döndürürse onu, yoksa yeni nesneyi döndür
return sonuc instanceof Object ? sonuc : nesne;
}
// Bu iki satır eşdeğer:
const a = new Kullanici("Ahmet", "ahmet@test.com");
const b = yeniOlustur(Kullanici, "Ahmet", "ahmet@test.com");Neden Metotları prototype'a Ekliyoruz?
// ❌ YANLIŞ — metodu constructor içinde tanımlamak
function KullaniciBad(ad) {
this.ad = ad;
// Her instance için AYRI fonksiyon oluşturulur — bellek israfı!
this.selamla = function () {
return `Merhaba, ben ${this.ad}`;
};
}
const u1 = new KullaniciBad("A");
const u2 = new KullaniciBad("B");
console.log(u1.selamla === u2.selamla); // false — iki farklı fonksiyon!
// 1000 kullanıcı = 1000 kopya fonksiyon → bellek israfı
// ✅ DOĞRU — metodu prototype'a eklemek
function KullaniciGood(ad) {
this.ad = ad;
}
KullaniciGood.prototype.selamla = function () {
return `Merhaba, ben ${this.ad}`;
};
const u3 = new KullaniciGood("A");
const u4 = new KullaniciGood("B");
console.log(u3.selamla === u4.selamla); // true — aynı fonksiyon!
// 1000 kullanıcı = 1 fonksiyon → bellek verimliPrototipal Kalıtım Uygulamak
Constructor fonksiyonlarla kalıtım zinciri oluşturmak biraz çetrefillidir:
// Üst "sınıf" — Hayvan
function Hayvan(isim, yas) {
this.isim = isim;
this.yas = yas;
}
Hayvan.prototype.bilgi = function () {
return `${this.isim}, ${this.yas} yaşında`;
};
Hayvan.prototype.sesCikar = function () {
return "..."; // Genel ses
};
// Alt "sınıf" — Kopek (Hayvan'dan kalıtır)
function Kopek(isim, yas, cins) {
// 1. Üst constructor'ı çağır — super() benzeri
Hayvan.call(this, isim, yas);
this.cins = cins;
}
// 2. Prototype zincirini bağla
Kopek.prototype = Object.create(Hayvan.prototype);
// 3. constructor referansını düzelt
Kopek.prototype.constructor = Kopek;
// 4. Alt sınıfa özel metotlar ekle
Kopek.prototype.sesCikar = function () {
return "Hav hav!"; // Override
};
Kopek.prototype.getir = function () {
return `${this.isim} topu getirdi!`;
};
// Test
const karabas = new Kopek("Karabaş", 3, "Golden");
console.log(karabas.bilgi()); // "Karabaş, 3 yaşında" — Hayvan'dan
console.log(karabas.sesCikar()); // "Hav hav!" — Kopek'te override
console.log(karabas.getir()); // "Karabaş topu getirdi!" — Kopek'e özel
console.log(karabas instanceof Kopek); // true
console.log(karabas instanceof Hayvan); // true
console.log(karabas instanceof Object); // trueBu kalıtım kodu karmaşık: call(), Object.create(), constructor düzeltmesi... ES6 class syntax'ı tam olarak bu karmaşıklığı gidermek için var. Ama arka planda hâlâ bu mekanizma çalışıyor.
Prototip Üzerinde Değişiklik
hasOwnProperty — Kimin Özelliği?
const hayvan = { canli: true };
const kedi = Object.create(hayvan);
kedi.isim = "Tekir";
// hasOwnProperty — nesnenin KENDİ özelliği mi?
console.log(kedi.hasOwnProperty("isim")); // true — kendi özelliği
console.log(kedi.hasOwnProperty("canli")); // false — prototipten geliyor
// for...in prototipal özellikleri de gezer!
for (const key in kedi) {
console.log(key, "→ kendi mi?", kedi.hasOwnProperty(key));
}
// isim → kendi mi? true
// canli → kendi mi? false
// Sadece kendi özelliklerini istiyorsan:
for (const key in kedi) {
if (kedi.hasOwnProperty(key)) {
console.log(key); // Sadece "isim"
}
}
// Veya daha modern:
console.log(Object.keys(kedi)); // ["isim"] — sadece kendi enumerable'lar
console.log(Object.values(kedi)); // ["Tekir"]
console.log(Object.entries(kedi)); // [["isim", "Tekir"]]Property Shadowing (Özellik Gölgeleme)
const prototip = {
deger: 42,
metot() {
return "prototipten";
},
};
const nesne = Object.create(prototip);
// Okuma — prototipten gelir
console.log(nesne.deger); // 42
console.log(nesne.metot()); // "prototipten"
// Yazma — nesnenin KENDİSİNE yazar (prototipi DEĞİŞTİRMEZ)
nesne.deger = 100;
nesne.metot = function () {
return "nesneden";
};
console.log(nesne.deger); // 100 — kendi özelliği (gölgeleme)
console.log(prototip.deger); // 42 — prototip değişmedi!
console.log(nesne.metot()); // "nesneden" — gölgelenmiş metot
console.log(prototip.metot()); // "prototipten" — orijinal💡 İpucu: Bir nesneye özellik yazdığında, prototype chain'deki aynı isimli özellik gölgelenir (shadow) ama değiştirilmez. Bu önemli bir güvenlik özelliğidir.
Yerleşik Prototipleri Anlama
JavaScript'teki tüm veri tipleri (primitifler hariç) bir prototip zincirine sahiptir:
// Dizi prototipi
const dizi = [1, 2, 3];
// dizi → Array.prototype → Object.prototype → null
console.log(dizi.map); // Array.prototype'tan
console.log(dizi.toString); // Array.prototype'tan (override edilmiş)
// String prototipi (primitif sarmalama)
const str = "merhaba";
// str → String.prototype → Object.prototype → null
console.log(str.toUpperCase); // String.prototype'tan
console.log(str.split); // String.prototype'tan
// Fonksiyon prototipi
function fn() {}
// fn → Function.prototype → Object.prototype → null
console.log(fn.call); // Function.prototype'tan
console.log(fn.bind); // Function.prototype'tanYerleşik Prototipleri Genişletme (Kaçının!)
// ⚠️ Built-in prototipleri değiştirmek TEHLİKELİ
// Yapılabilir ama YAPILMAMALI
// ❌ Kötü pratik — Array.prototype'a metot ekleme
Array.prototype.ilk = function () {
return this[0];
};
console.log([10, 20, 30].ilk()); // 10 — çalışır AMA...
// - Gelecekte aynı isimde native metot eklenebilir (çakışma)
// - for...in döngüsünde görünür (beklenmedik davranış)
// - Başka kütüphanelerle çakışabilir
// - "Monkey patching" — debug kabusu
// ✅ İyi pratik — yardımcı fonksiyon kullan
function ilkEleman(dizi) {
return dizi[0];
}
// ✅ Veya class ile extend et
class SuperArray extends Array {
ilk() {
return this[0];
}
son() {
return this[this.length - 1];
}
}
const arr = SuperArray.from([10, 20, 30]);
console.log(arr.ilk()); // 10
console.log(arr.son()); // 30Yaygın Hatalar ve Tuzaklar
Hata 1: new Olmadan Constructor Çağırmak
// ❌ new unutulursa this → global (veya undefined strict mode'da)
function Kullanici(ad) {
this.ad = ad;
}
const k = Kullanici("Ahmet"); // new YOK!
console.log(k); // undefined — constructor return etmiyor
// console.log(window.ad); // "Ahmet" — global'e yazıldı! (non-strict)
// ✅ Koruma: new.target kontrolü
function GüvenliKullanici(ad) {
if (!new.target) {
throw new Error("Bu fonksiyon new ile çağrılmalıdır!");
// veya: return new GüvenliKullanici(ad);
}
this.ad = ad;
}
// GüvenliKullanici("Ahmet"); // Error: new ile çağrılmalıdır!
new GüvenliKullanici("Ahmet"); // ✅ Doğru kullanımHata 2: Prototype'ı Yanlış Bağlamak
// ❌ Prototype'ı düz nesne olarak atamak — constructor kaybolur
function Hayvan(isim) {
this.isim = isim;
}
function Kedi(isim) {
Hayvan.call(this, isim);
}
// YANLIŞ — Hayvan'ın prototype'ını direkt atama
Kedi.prototype = Hayvan.prototype;
// Artık Kedi.prototype === Hayvan.prototype
// Kedi'ye eklenen metotlar Hayvan'a da eklenir!
Kedi.prototype.miyavla = function () { return "Miyav!"; };
// Hayvan.prototype.miyavla da artık var — YANLIŞ!
// ✅ DOĞRU — Object.create ile yeni nesne oluştur
Kedi.prototype = Object.create(Hayvan.prototype);
Kedi.prototype.constructor = Kedi;
// Artık Kedi.prototype AYRI bir nesne — Hayvan'ı etkilemezHata 3: for...in ile Prototipal Özellikler
const proto = { inherited: true };
const nesne = Object.create(proto);
nesne.own = true;
// ❌ for...in prototipal özellikleri de döndürür
for (const key in nesne) {
console.log(key); // "own", "inherited"
}
// ✅ Object.keys sadece kendi özelliklerini döndürür
for (const key of Object.keys(nesne)) {
console.log(key); // "own"
}
// ✅ veya hasOwnProperty kontrolü
for (const key in nesne) {
if (Object.hasOwn(nesne, key)) { // ES2022 — daha güvenli
console.log(key); // "own"
}
}Gerçek Dünya Örneği: Olay Sistemi (Event Emitter)
// Prototipal kalıtım ile Event Emitter
function EventEmitter() {
this._events = {};
}
EventEmitter.prototype.on = function (event, listener) {
if (!this._events[event]) {
this._events[event] = [];
}
this._events[event].push(listener);
return this; // Method chaining için
};
EventEmitter.prototype.emit = function (event, ...args) {
const listeners = this._events[event];
if (listeners) {
listeners.forEach((fn) => fn.apply(this, args));
}
return this;
};
EventEmitter.prototype.off = function (event, listener) {
const listeners = this._events[event];
if (listeners) {
this._events[event] = listeners.filter((fn) => fn !== listener);
}
return this;
};
// Kullanıcı sınıfı — EventEmitter'dan kalıtır
function User(ad) {
EventEmitter.call(this); // Üst constructor
this.ad = ad;
this.aktif = false;
}
User.prototype = Object.create(EventEmitter.prototype);
User.prototype.constructor = User;
User.prototype.girisYap = function () {
this.aktif = true;
this.emit("login", this);
return this;
};
User.prototype.cikisYap = function () {
this.aktif = false;
this.emit("logout", this);
return this;
};
// Kullanım
const ahmet = new User("Ahmet");
ahmet
.on("login", (user) => console.log(`${user.ad} giriş yaptı!`))
.on("logout", (user) => console.log(`${user.ad} çıkış yaptı!`));
ahmet.girisYap(); // "Ahmet giriş yaptı!"
ahmet.cikisYap(); // "Ahmet çıkış yaptı!"
// instanceof kontrolleri
console.log(ahmet instanceof User); // true
console.log(ahmet instanceof EventEmitter); // trueÖzet
Bu derste JavaScript'in kalıtım modelinin temellerini öğrendik:
JavaScript'te kalıtım prototipaldir — class-based değil. Her nesne bir prototipe bağlıdır ve özellikler prototype chain üzerinden yukarı doğru aranır.
`Object.create(proto)` belirtilen prototipi kullanarak yeni nesne oluşturur — prototipal kalıtımın en saf biçimidir.
Constructor fonksiyonlar +
newkeyword'ü, ES6 class'larının arka planını oluşturur.newarkada 4 adım yapar: boş nesne oluştur, prototipi bağla, constructor'ı çalıştır, nesneyi döndür.Metotları prototype'a eklemek bellek verimlidir — tüm instance'lar aynı fonksiyonu paylaşır.
Property shadowing: Nesneye özellik yazmak prototipi değiştirmez, onu gölgeler.
Yerleşik prototipleri (Array.prototype vb.) değiştirmek tehlikelidir — kaçınılmalıdır.
Bir sonraki derste, bu prototipal altyapının üzerine inşa edilen ES6 class syntax'ını öğreneceğiz.
AI Asistan
Sorularını yanıtlamaya hazır