İçeriğe geç
JavaScript

JavaScript Prototypes ve Inheritance Mekanizması

Tarık Tunç
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;
}
İpucu: Metotları 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}'
Uyarı: Mixin'ler isim çakışması riski taşır. İki mixin aynı metot adını tanımlıyorsa, son eklenen kazanır. Symbol kullanarak bu riski azaltabilirsiniz.
Bilgi: ES6 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.

Kaynaklar

İlgili Yazılar