İçeriğe geç
Educora
İleri20 dk16 / 27

Prototipler ve kalıtım

`class`'ın arkasında gerçekte ne olduğunu gör: prototip zinciri, `Object.create`, yapıcı fonksiyonlar ve sınıfların neden “sözdizimsel şeker” olarak adlandırıldığı.

Kendini test et
Bu derste öğreneceklerin
  • Özellik aramasının prototip zinciri boyunca Object.prototype'a ve null'a kadar nasıl ilerlediğini açıklamak
  • Object.create ile ve new ile çağrılan yapıcı fonksiyonlarla nesneler oluşturmak
  • class'ın prototipler üzerine kurulduğunu göstermek ve kendi özellikleri kalıtılanlardan ayırt etmek

Her dizinin bir map metodu, her string'in bir toUpperCase metodu var; oysa bu metotları dizilerine hiç sen yazmadın. Peki nereden geliyorlar? JavaScript'te her nesnenin başka bir nesneye gizli bir bağlantısı vardır: prototipine. Bir özellik nesnenin kendisinde bulunmazsa JavaScript prototipe bakar. Yerleşik metotlar, kalıtım ve sınıflar bu basit mekanizmanın üzerine kuruludur.

Prototip zinciri

Bir nesnenin prototipini Object.getPrototypeOf(obj) ile okuyabilirsin (eski __proto__ yazımı da çalışır ama önerilmez). obj.x okunurken JavaScript önce nesnenin kendi özelliklerine, sonra prototipine, sonra prototipin prototipine bakar ve null'a ulaşana kadar devam eder. Özellik hiçbir yerde yoksa sonuç undefined olur. Yazma ise farklı çalışır: obj.x = ... her zaman nesnenin kendi özelliğini oluşturur ya da değiştirir, prototipe dokunmaz.

JavaScript
const animal = {
  eats: true,
  describe() {
    return `${this.name} eats: ${this.eats}`;
  },
};

const cat = Object.create(animal);
cat.name = 'Tom';

console.log(cat.describe());
console.log(Object.getPrototypeOf(cat) === animal);
console.log(Object.hasOwn(cat, 'name'), Object.hasOwn(cat, 'eats'));

cat.eats = false;
console.log(cat.eats, animal.eats);

console.log(Object.getPrototypeOf(animal) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype));
▸ Beklenen çıktı
Tom eats: true
true
true false
false true
true
null
cat.eats = false, kalıtılan özelliği “gölgeleyen” kendi özelliğini oluşturdu; animal değişmedi. Zincir: cat → animal → Object.prototype → null.

Yerleşik nesneler de aynı şekilde çalışır. Bir dizinin zinciri şöyledir: dizi → Array.prototype → Object.prototype → null. map dizinin kendisinde değil, Array.prototype içindedir. in operatörü tüm zinciri, Object.hasOwn ise yalnızca kendi özellikleri kontrol eder. Array.prototype'ın kendi toString'i, Object.prototype.toString'i gölgeler.

JavaScript
const numbers = [1, 2, 3];

console.log(Object.getPrototypeOf(numbers) === Array.prototype);
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype);
console.log(Object.hasOwn(numbers, 'map'), 'map' in numbers);
console.log(numbers.toString === Object.prototype.toString);
console.log(numbers.toString());
▸ Beklenen çıktı
true
true
false true
false
1,2,3

Yapıcı fonksiyonlar ve new

class ortaya çıkmadan önce aynı yapıdaki nesneler yapıcı fonksiyonlarla oluşturulurdu. Herhangi bir normal fonksiyonu new ile çağırdığında JavaScript dört adım atar:

  1. boş bir nesne oluşturur;
  2. prototipini Fn.prototype yapar;
  3. fonksiyonu, this yeni nesne olacak şekilde çalıştırır;
  4. fonksiyon başka bir nesne döndürmüyorsa yeni nesneyi döndürür.

Metotlar Fn.prototype'a yazılır: böylece her nesne kendi kopyasını taşımak yerine tüm örnekler her metodun tek bir kopyasını paylaşır. Bin nesneli bir programda bu gerçek bir bellek tasarrufudur.

JavaScript
function Student(name, grade) {
  this.name = name;
  this.grade = grade;
}

Student.prototype.introduce = function () {
  return `${this.name}, grade ${this.grade}`;
};

const leyla = new Student('Leyla', 9);
const elvin = new Student('Elvin', 10);

console.log(leyla.introduce(), '|', elvin.introduce());
console.log(leyla.introduce === elvin.introduce);
console.log(Object.getPrototypeOf(leyla) === Student.prototype);
console.log(leyla);
▸ Beklenen çıktı
Leyla, grade 9 | Elvin, grade 10
true
true
Student { name: 'Leyla', grade: 9 }

Sınıflar sözdizimsel şekerdir

class (ES2015) aynı yapıyı daha okunaklı yazmanı sağlar: sınıf aslında bir fonksiyondur, metotları ClassName.prototype'a yerleşir, extends ise prototipleri birbirine bağlar. Farklar da vardır: sınıf kodu her zaman katı moddadır, sınıf new olmadan çağrılamaz, metotlar for...in içinde görünmez ve #gizli alanlar yalnızca sınıflarda bulunur.

JavaScript
class Shape {
  constructor(name) { this.name = name; }
  describe() { return `I am a ${this.name}`; }
}

class Circle extends Shape {
  constructor(radius) {
    super('circle');
    this.radius = radius;
  }
  area() { return +(Math.PI * this.radius ** 2).toFixed(2); }
}

const c = new Circle(2);
console.log(typeof Shape, c.describe(), c.area());
console.log(Object.getPrototypeOf(Circle.prototype) === Shape.prototype);
console.log(Object.getOwnPropertyNames(Circle.prototype));
console.log(Object.hasOwn(c, 'area'), 'area' in c);

try {
  Shape('square');
} catch (error) {
  console.log(error.name);
}
▸ Beklenen çıktı
function I am a circle 12.57
true
[ 'constructor', 'area' ]
false true
TypeError
Sınıf yazımıPerde arkasında
class A { m() {} }function A() {} + A.prototype.m = function () {}
class B extends A {}Object.setPrototypeOf(B.prototype, A.prototype)
super.m()A.prototype.m.call(this)
static s() {}A.s = function () {}
Tablo basitleştirilmiş bir haritadır: extends, statik metotların da kalıtılması için B'yi A'ya bağlar.
Alıştırma

Object.create(vehicle) ile iki nesne oluştur: car (brand: 'Toyota') ve bike (brand: 'BMX' ve kendi özelliği wheels: 2). describe() metodu ikisinde de prototipten gelmeli. Aşağıdaki satırlar sonucu yazdıracak.

Alıştırma · JavaScript
const vehicle = {
  wheels: 4,
  describe() {
    return `${this.brand} has ${this.wheels} wheels`;
  },
};

// create car and bike here

console.log(car.describe());
console.log(bike.describe());
console.log(Object.hasOwn(car, 'wheels'), Object.hasOwn(bike, 'wheels'));
▸ Beklenen çıktı
Toyota has 4 wheels
BMX has 2 wheels
false true
Alıştırma

Counter.prototype'a bir increment() metodu ekle: value'yu 1 artırsın ve çağrıların zincirlenebilmesi için this döndürsün. Metot her nesnenin kendi özelliği değil, tüm örneklerin ortak metodu olmalı.

Alıştırma · JavaScript
function Counter(start) {
  this.value = start;
}

// add increment() to Counter.prototype

const a = new Counter(5);
const b = new Counter(0);
a.increment().increment();

console.log(a.value, b.value);
console.log(a.increment === b.increment);
console.log(Object.hasOwn(a, 'increment'));
▸ Beklenen çıktı
7 0
true
false

Önemli noktalar

  • Her nesnenin prototipe gizli bir bağlantısı vardır; olmayan bir özellik zincir boyunca Object.prototype'a, sonra null'a kadar aranır.
  • Bir özelliğe yazmak her zaman kalıtılanı gölgeleyen kendi özelliğini oluşturur; prototip değişmez.
  • Object.create(proto) verilen prototipe sahip bir nesne oluşturur; new Fn() yeni nesneyi Fn.prototype'a bağlar.
  • Prototipteki metotları tüm örnekler paylaşır; Object.hasOwn kendi özellikleri, in ise tüm zinciri kontrol eder.
  • class prototipler üzerine kuruludur ve ek olanaklar sunar: katı mod, zorunlu new ve gizli alanlar.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
const a = { x: 1 }; const b = Object.create(a); b.x = 2; sonrasında a.x neye eşittir?