JavaScript Prototypes ve Inheritance Mekanizması

JavaScript Prototypes ve Inheritance Mekanizması
JavaScript, sınıf tabanlı değil prototip tabanlı bir nesne yönelimli dildir. Her nesne, başka bir nesneden miras alır ve bu miras zinciri prototip zinciri (prototype chain) olarak adlandırılır. ES6 ile gelen class sözdizimi bu mekanizmayı gizlese de, perde arkasında hâlâ prototip sistemi çalışır. Bu yazıda prototip zincirinin derinliklerini keşfediyoruz.
1. Prototip Zinciri
JavaScript'te her nesnenin gizli bir [[Prototype]] referansı vardır. Bir özelliğe erişildiğinde, JavaScript önce nesnenin kendisine bakar, bulamazsa prototip zincirinde yukarı doğru arar.
// Prototip zinciri oluşturma
const hayvan = {
canli: true,
nefesAl() {
console.log("Nefes alıyor...");
}
};
const kedi = Object.create(hayvan);
kedi.miyavla = function() {
console.log("Miyav!");
};
const tekir = Object.create(kedi);
tekir.ad = "Boncuk";
// Prototip zinciri: tekir -> kedi -> hayvan -> Object.prototype -> null
tekir.miyavla(); // "Miyav!" (kedi'den)
tekir.nefesAl(); // "Nefes alıyor..." (hayvan'dan)
console.log(tekir.canli); // true (hayvan'dan)
console.log(tekir.ad); // "Boncuk" (kendisinden)
// Zincir kontrolü
console.log(Object.getPrototypeOf(tekir) === kedi); // true
console.log(Object.getPrototypeOf(kedi) === hayvan); // true
console.log(tekir.hasOwnProperty("ad")); // true
console.log(tekir.hasOwnProperty("canli")); // false
2. Constructor Fonksiyonlar ve prototype Özelliği
ES6 öncesinde sınıf benzeri yapılar constructor fonksiyonlar ile oluşturuluyordu. new anahtar kelimesi ile çağrılan fonksiyonlar, otomatik olarak prototype özelliğini kullanan nesneler oluşturur.
// Constructor fonksiyon
function Kullanici(ad, email) {
this.ad = ad;
this.email = email;
this.olusturma = Date.now();
}
// Prototype üzerinde metot tanımlama
Kullanici.prototype.selamla = function() {
return `Merhaba, ben ${this.ad}`;
};
Kullanici.prototype.emailGoster = function() {
return this.email.replace(/(.{2})(.*)(@.*)/, "$1***$3");
};
const k1 = new Kullanici("Tarık", "tarik@example.com");
const k2 = new Kullanici("Ayşe", "ayse@example.com");
console.log(k1.selamla()); // "Merhaba, ben Tarık"
// Metotlar paylaşılır, bellekte tek kopya
console.log(k1.selamla === k2.selamla); // true
// new olmadan çağrı koruması
function GuvenliConstructor(deger) {
if (!(this instanceof GuvenliConstructor)) {
return new GuvenliConstructor(deger);
}
this.deger = deger;
}
prototype üzerinde tanımlamak, her instance'ın aynı fonksiyon referansını paylaşmasını sağlar. Bu, metotları constructor içinde tanımlamaya göre çok daha bellek dostu bir yaklaşımdır.
3. ES6 Class Sözdizimi
ES6 class'ları, prototip tabanlı kalıtımın üzerine sözdizimsel şeker (syntactic sugar) sunar. Daha okunabilir ve hata yapmaya daha az açık bir yapı sağlar.
class Sekil {
#renk; // Private alan (ES2022)
constructor(x, y, renk = "siyah") {
this.x = x;
this.y = y;
this.#renk = renk;
}
// Getter
get konum() {
return `(${this.x}, ${this.y})`;
}
// Setter
set renk(yeniRenk) {
const gecerliRenkler = ["kırmızı", "mavi", "yeşil", "siyah"];
if (!gecerliRenkler.includes(yeniRenk)) {
throw new Error(`Geçersiz renk: ${yeniRenk}`);
}
this.#renk = yeniRenk;
}
get renk() {
return this.#renk;
}
// Statik metot
static mesafe(s1, s2) {
return Math.sqrt((s1.x - s2.x) ** 2 + (s1.y - s2.y) ** 2);
}
alanHesapla() {
throw new Error("Alt sınıf tarafından implement edilmeli");
}
}
// Kalıtım
class Daire extends Sekil {
#yaricap;
constructor(x, y, yaricap, renk) {
super(x, y, renk); // Üst sınıf constructor'ı
this.#yaricap = yaricap;
}
alanHesapla() {
return Math.PI * this.#yaricap ** 2;
}
cevreHesapla() {
return 2 * Math.PI * this.#yaricap;
}
toString() {
return `Daire(${this.konum}, r=${this.#yaricap})`;
}
}
const d = new Daire(0, 0, 5, "kırmızı");
console.log(d.alanHesapla()); // 78.53...
console.log(d.toString()); // "Daire((0, 0), r=5)"
console.log(d instanceof Daire); // true
console.log(d instanceof Sekil); // true
4. Mixin Pattern'i
JavaScript tek kalıtımı destekler, ancak mixin pattern'i ile birden fazla kaynaktan davranış eklenebilir.
// Mixin tanımlama
const Serializeable = {
toJSON() {
const sonuc = {};
for (const [anahtar, deger] of Object.entries(this)) {
sonuc[anahtar] = deger;
}
return JSON.stringify(sonuc);
},
fromJSON(json) {
Object.assign(this, JSON.parse(json));
return this;
}
};
const EventEmitter = {
_olaylar: null,
on(olay, dinleyici) {
if (!this._olaylar) this._olaylar = {};
(this._olaylar[olay] ||= []).push(dinleyici);
return this;
},
emit(olay, ...args) {
if (!this._olaylar?.[olay]) return;
this._olaylar[olay].forEach(fn => fn(...args));
}
};
// Mixin uygulama
class Urun {
constructor(ad, fiyat) {
this.ad = ad;
this.fiyat = fiyat;
}
}
Object.assign(Urun.prototype, Serializeable, EventEmitter);
const urun = new Urun("Laptop", 25000);
urun.on("fiyatDegisti", (yeni) => console.log(`Yeni fiyat: ${yeni}`));
console.log(urun.toJSON()); // '{"ad":"Laptop","fiyat":25000}'
class sözdizimi prototip sisteminin üzerine bir katmandır. class Daire extends Sekil yazmak, aslında Daire.prototype = Object.create(Sekil.prototype) ile eşdeğerdir.
Sonuç
JavaScript'in prototip tabanlı kalıtım sistemi, dilin en temel mekanizmalarından biridir. Prototip zincirini, constructor fonksiyonları, ES6 class sözdizimini ve mixin pattern'ini anlamak, etkin nesne yönelimli JavaScript kodu yazmanın anahtarıdır. Class sözdizimi modern projeler için tercih edilse de, prototip sistemini bilmek hata ayıklama ve performans optimizasyonu için vazgeçilmezdir.