JavaScript’te Prototip Mekanizması Nasıl Çalışır?
JavaScript’te bulunan prototype (Türkçe: Prototip) mekanizması, dilin belki de en önemli ve en güçlü özelliğidir. Zira JavaScript, sınıf-tabanlı bir dil olmadığı için, obje oluşturmak ve bunları birbirine bağlamak için prototip tabanlı bir sistem kullanır.
JavaScript’te Prototip Mekanizması
Prototip kavramı, JavaScript’teki nesnelerin birbirlerinden özellik ve metotları miras almasını sağlayan temel mekanizmadır. avaScript’in bu özelliği, onu diğer sınıf-tabanlı programlama dillerinden ayıran en önemli faktördür. Çoğu dilde, miras ve nesne genişletme işlemleri sınıflar aracılığıyla gerçekleştirilirken, JavaScript’te bu, “prototip zinciri” sayesinde olmaktadır.
Bu yüzden JavaScript, prototip-tabanlı bir programlama dili olarak anılır. Başka bir deyişle, JavaScript’te her şey bir nesne olarak kabul edilir ve bu nesneler, prototipler üzerinden türetilir.
Ama böyle bir mekanizmanın ne gibi bir yararı var? İlk olarak, prototip mekanizması sayesinde bir nesne üzerinde olmayan bir özelliği veya metodu aradığımızda, JavaScript otomatik olarak o nesnenin prototipine bakar. Eğer orada da bulamazsa, bu zincir, prototipin kendi prototipine bakarak devam eder. Bu “prototip zinciri” sayesinde, çok daha modüler ve yeniden kullanılabilir kodlar yazabiliriz.
Örneğin, JavaScript’teki tüm diziler Array
prototipinden türetilmektedir. Eğer Array
prototipine bir metot eklersek, bu metod otomatik olarak tüm dizilerde kullanılabilir hale gelmektedir.
Bir diğer avantajı, mevcut nesneleri genişletebilme yeteneğimizdir. Yani, bir nesnenin prototipine yeni bir özellik veya metot eklediğimizde, bu özellik veya metot otomatik olarak o nesnenin tüm örneklerine eklenir. Haliyle kod tekrarı önlenecek, daha temiz, düzenli bir yapı oluşturmamıza yardımcı olacaktır.
Fonksiyon Prototipleri (Function Prototype)
JavaScript’te fonksiyonlar da aslında birer nesnedir. Evet, doğru duydunuz! Bu durum, fonksiyonların da özelliklere (properties) ve metotlara sahip olabileceği anlamına gelmektedir. Fonksiyonlar, Function
adında özel bir nesne türünden türetilir. Ve her fonksiyon, doğuştan bir prototype
özelliğine sahiptir.
“Peki bu prototype
özelliği ne işe yarar?” diye sorabilirsiniz. Cevap olarak; “fonksiyon prototipleri, o fonksiyon ile oluşturulan nesne örneklerine özellikler ve metotlar eklemek için kullanılır” diyebiliriz. Yani, bir fonksiyonun prototype
özelliğine bir özellik veya metot eklediğimizde, bu fonksiyonla oluşturulan tüm nesne örnekleri bu özelliği veya metodu miras alır.
Bu prototip özelliği, fonksiyon yapıcı (constructor) bir fonksiyon olarak kullanıldığında önem kazanır (yani new anahtar kelimesiyle birlikte çağrıldığında). Bu durumda, yeni oluşturulan nesne, yapıcı fonksiyonun prototype özelliğini miras alır. Bakınız:
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("Merhaba, benim adım " + this.name);
};
let person1 = new Person("John");
person1.sayHello(); // Çıktı: "Merhaba, benim adım John"
Yukarıdaki örnekte, sayHello
adlı bir metot Person
fonksiyonunun prototipine ekleniyor. Böylece, Person
fonksiyonu ile oluşturulan her nesne örneği bu metodu kullanabilir.
Fonksiyon prototiplerinin en büyük avantajı, hafıza verimliliğidir. Eğer her nesne örneği için bu metodu ayrı ayrı tanımlasaydık gereksiz bir hafıza kullanımı ortaya çıkacaktı. Ancak prototip aracılığıyla bu metot bir kez tanımlanır ve tüm nesne örnekleri bu metodu paylaşır.
Prototip Zinciri (Prototype Chain)
JavaScript’te, bir nesne üzerinde bir özellik arandığında ve bu özellik mevcut nesnede bulunamazsa, JavaScript otomatik olarak o nesnenin prototipine bakar. Eğer orada da bu özellik bulunamazsa, bu kez prototipin kendi prototipine bakar. Bu işlem, özellik bulunana kadar veya prototip zincirinin sonuna gelene kadar devam eder. İşte bu sürece “prototip zinciri” denir.
Örnek üzerinden gösterelim:
function Person() {
this.name = "John";
}
Person.prototype.sayHello = function() {
console.log("Merhaba, ben " + this.name);
};
function Student() {
this.studentID = "12345";
}
// Student'ın prototipini Person'a bağlayalım.
Student.prototype = new Person();
let student1 = new Student();
console.log(student1.name); // "John"
student1.sayHello(); // "Merhaba, ben John"
console.log(student1.studentID); // "12345"
Bu örnekte, Student
isimli bir fonksiyon oluşturduk ve onun prototipini Person
fonksiyonundan türetilmiş bir nesne ile değiştirdik. Bu yüzden, bir Student
nesnesi oluşturduğumuzda, hem Student
hem de Person
fonksiyonlarındaki özelliklere ve metodlara erişebiliriz.
Eğer student1
nesnesinde bir özellik veya metot aranırsa ve bu nesnede bulunamazsa, JavaScript otomatik olarak Student
fonksiyonunun prototipine (yani Person
fonksiyonuna) bakacaktı.
JavaScript’teki tüm nesneler, varsayılan olarak Object.prototype
objesinden özellikler ve metotlar miras alır. Örneğin, toString
metodu Object.prototype
objesinde tanımlanmış bir metottur ve bu nedenle tüm nesnelerde kullanılabilir. Örneğin:
let myArray = [1, 2, 3];
let myDate = new Date();
let myString = "Merhaba, Dünya!";
console.log(myArray.toString()); // "1,2,3"
console.log(myDate.toString()); // Örneğin: "Fri Oct 20 2023 12:34:56 GMT+0300 (GMT+03:00)"
console.log(myString.toString()); // "Merhaba, Dünya!"
Bu örnekte, farklı türlerdeki nesnelerin (Array
, Date
ve String
) hepsi toString
metodunu kullanabiliyor. Bunun nedeni, bu metodun Object.prototype
içerisinde tanımlı olması ve tüm nesnelerin bu prototipten miras almasıdır.