ECMAScript Sınıf veya Nesne Tanımlama
- Önceki Sayfa Nesne Etki Alanı
- Sonraki Sayfa Nesne Düzenleme
Önceden tanımlanmış nesneleri kullanmak, yönelimli nesne dili dilinin sadece bir kısmıdır; gerçek gücü, kendi özel sınıfları ve nesneleri oluşturmakta yatmaktadır.
ECMAScript, nesneleri veya sınıfları oluşturmak için birçok yöntem sunar.
Fabrika yöntemi
Orjinal yöntem
Nesnelerin özellikleri nesnenin oluşturulduktan sonra dinamik olarak tanımlanabilir, bu yüzden birçok geliştirici JavaScript'in ilk tanıtılmasında aşağıdaki gibi kod yazmıştır:
var oCar = new Object; oCar.color = "blue"; oCar.doors = 4; oCar.mpg = 25; oCar.showColor = function() { alert(this.color); };
Yukarıdaki kodda, car nesnesi oluşturuldu. Daha sonra ona bazı özellikler atanır: rengi mavi, dört kapı ve her galon için 25 mil gidebilir. Son özellik, bir fonksiyona işaret eden bir işaretçidir, bu da bu özelliğin bir yöntem olduğu anlamına gelir. Bu kodu çalıştırdıktan sonra, car nesnesini kullanabilirsiniz.
Ancak burada bir sorun var, birden fazla car örneği oluşturmak gerekebilir.
Çözüm: Fabrika yöntemi
Bu sorunu çözmek için, geliştiriciler belirli türdeki nesneleri oluşturup döndürebilen fabrika fonksiyonları (factory function) yaratmışlardır.
Örneğin, createCar() fonksiyonu, önceki olarak sıralanan car nesnesi oluşturma operasyonlarını içe aktarabilir:
function createCar() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; } var oCar1 = createCar(); var oCar2 = createCar();
Burada, ilk örnekteki tüm kodlar createCar() fonksiyonu içinde yer alıyor. Ayrıca, bir satır ekstra kod var ve car nesnesi olarak (oTempCar) fonksiyon değerini döndürür. Bu fonksiyonu çağırarak, yeni bir nesne oluşturulur ve ona tüm gerekli özellikler verilir, önceki açıklamamızdaki car nesnesinin bir kopyası oluşturulur. Bu şekilde, car nesnesinin iki sürümünü (oCar1 ve oCar2) kolayca oluşturabiliriz, bu sürümlerin özellikleri tamamen aynıdır.
Fonksiyona parametre iletilmesi
createCar() fonksiyonunu değiştirerek, özelliklerin varsayılan değerlerini sadece atamak yerine, her bir özelliğe varsayılan değerleri iletilmeyi de yapabiliriz:
function createCar(sColor,iDoors,iMpg) { var oTempCar = new Object; oTempCar.color = sColor; oTempCar.doors = iDoors; oTempCar.mpg = iMpg; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; } var oCar1 = createCar("red",4,23); var oCar2 = createCar("blue",3,25); oCar1.showColor(); // Çıktı "red" oCar2.showColor(); // Çıktı "blue"
createCar() fonksiyonuna parametre ekleyerek, oluşturulacak car nesnesinin color, doors ve mpg özelliklerine değer atayabilirsiniz. Bu, iki nesnenin aynı özelliklere sahip olmasına rağmen farklı özellik değerlerine sahip olmasını sağlar.
工厂函数 dışında nesne yöntemlerini tanımlayın
ECMAScript her zaman daha resmi hale gelmeye çalışsa da, nesne oluşturma yöntemleri göz ardı edilmiş ve bu yöntemlerin standartlaştırılması hala karşı çıkanlar tarafından eleştiriliyor. Biri semantik bir nedenle (new operatörü ile birlikte kullanılan yapıcı fonksiyon gibi resmi görünmüyor), diğeri ise işlevsel bir nedenle. İşlevsel neden, bu şekilde nesne oluşturma yöntemleri gerektiriyor. Önceki örnekte, her seferinde createCar() fonksiyonunu çağırıldığında, yeni bir showColor() fonksiyonu oluşturuluyor, bu da her nesnenin kendi showColor() sürümüne sahip olduğu anlamına geliyor. Ancak aslında, her nesne aynı fonksiyonu paylaşır.
Bazı geliştiriciler, bu sorunu önlemek için fabrika fonksiyonunun dışında nesne yöntemlerini tanımlar ve bu yöntemlere bir işaret ederek bu sorunu çözerler:}
function showColor() { alert(this.color); } function createCar(sColor,iDoors,iMpg) { var oTempCar = new Object; oTempCar.color = sColor; oTempCar.doors = iDoors; oTempCar.mpg = iMpg; oTempCar.showColor = showColor; return oTempCar; } var oCar1 = createCar("red",4,23); var oCar2 = createCar("blue",3,25); oCar1.showColor(); // Çıktı "red" oCar2.showColor(); // Çıktı "blue"
Bu kodun yeniden yazıldığı bu bölümde, createCar() fonksiyonu öncesinde showColor() fonksiyonu tanımlanmıştır. createCar() içersinde, varolan showColor() fonksiyonuna bir işaret eden bir nesneye bir işaret atanmıştır. İşlevsel olarak, bu, fonksiyon nesnesi oluşturma sorununu çözmüş gibi görünüyor; ancak semantik olarak, bu fonksiyon, nesne yöntemi gibi görünmüyor.
Tüm bu sorunlarGeliştirici tarafından tanımlanmıştıryapıcı fonksiyonun ortaya çıkışı.
Yapıcı fonksiyon yöntemi
Yapıcı fonksiyonu oluşturmak, fabrika fonksiyonu gibi kolaydır. İlk adım, sınıf adını, yani yapıcı fonksiyonun adını seçmektir. Geleneksel olarak, bu ismin ilk harfi büyük harf ile yazılır, böylece genellikle küçük harflerle yazılan değişken adlarından ayrılır. Bu farklılık dışında, yapıcı fonksiyon, fabrika fonksiyonuna benzer görünüyor. Aşağıdaki örneği göz önünde bulundurun:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.showColor = function() { alert(this.color); }; } var oCar1 = new Car("red",4,23); var oCar2 = new Car("blue",3,25);
Üstteki kodun fabrika yöntemi ile arasındaki farkı size açıklayacağım. Öncelikle, yapıcı fonksiyonda nesne oluşturulmaz, bunun yerine this anahtarı kullanılır. new operatörü ile yapıcı fonksiyonu çalıştırırken, ilk satır kodun çalışmadan önce bir nesne oluşturulur, bu nesneye erişmek için sadece this kullanılır. Daha sonra this özelliklerine doğrudan atama yapılabilir, varsayılan olarak bu, yapıcı fonksiyonun geri dönüş değeri olur (return operatörünü açıkça kullanmak zorunda değilsiniz).
Şimdi, new işlev operatörü ve Car sınıfı ile nesneler oluşturmak, ECMAScript'teki genel nesne oluşturma yöntemine daha benzer hale geldi.
Bu yöntemin, fonksiyon yönetiminde önceki yöntemdeki aynı sorunlar olup olmadığını sorgulamak ister misiniz? Evet.
Fabrika fonksiyonları gibi, yapıcı fonksiyonlar da fonksiyonları tekrar oluşturur, her nesne için bağımsız fonksiyon sürümleri oluşturur. Ancak, fabrika fonksiyonları gibi, yapıcı fonksiyonu dışarıdan bir fonksiyonla yeniden yazmak da semantik açıdan anlamsızdır. Bu, aşağıda anlatacağımız prototip yönteminin avantajlarından biridir.
Prototip yöntemi
Bu yöntem, nesnenin prototype özelliğini kullanır, bu da yeni nesnelerin oluşturulmasına dayalı prototip olarak görülebilir.
Burada, önce boş bir yapıcı fonksiyon ile sınıf adını ayarlıyoruz. Sonra tüm özellikler ve yöntemler doğrudan prototype özelliğine atanır. Önceki örneği yeniden yazdık, kod şu şekilde:
function Car() { } Car.prototype.color = "blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car(); var oCar2 = new Car();
Bu kod parçasında, önce (Car) adında bir yapıcı fonksiyon tanımlanır, içinde hiçbir kod yok. Sonraki satırlar, Car'ın prototype özelliğine eklenen özellikler aracılığıyla Car nesnesinin özelliklerini tanımlar. new Car() çağrısı yapıldığında, prototipin tüm özellikleri hemen oluşturulan nesneye atanır, bu da tüm Car örneklerinin showColor() fonksiyonuna yönlendiren işaretler içerdiğini anlamına gelir. Semantik olarak, tüm özellikler bir nesneye ait gibi görünüyor, bu da önceki iki yöntemin sorunlarını çözmüştür.
Ayrıca, bu şekilde, instanceof işlev operatörü ile belirli bir değişkenin nesne türünü kontrol edebilirsiniz. Bu nedenle, aşağıdaki kod TRUE çıktısı verecektir:
alert(oCar1 instanceof Car); // Çıktı "true"
Prototip yönteminin sorunları
Prototip yöntemi iyi bir çözüm gibi görünüyor. Ne yazık ki, bu tamamen tatmin edici değil.
Öncelikle, bu yapıcı fonksiyon parametre almadı. Prototip yöntemi ile, yapıcı fonksiyona parametre göndererek özellik değerlerini başlatamayız, çünkü Car1 ve Car2'nin color özellikleri "blue"e, doors özellikleri 4e, mpg özellikleri 25e eşittir. Bu, özelliklerin varsayılan değerlerini değiştirmek için nesnelerin oluşturulmasından sonra beklenmesi gerektiği anlamına gelir, bu çok rahatsız edici, ancak bu da bitmiyor. Asıl sorun, özelliklerin fonksiyon değil nesnelere işaret ettiğinde ortaya çıkar. Fonksiyon paylaşımı sorun yaratmaz, ancak nesneler genellikle çok sayıda örnek arasında paylaşılır. Aşağıdaki örneği düşünün:
function Car() { } Car.prototype.color = "blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.drivers = new Array("Mike","John"); Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car(); var oCar2 = new Car(); oCar1.drivers.push("Bill"); alert(oCar1.drivers); // "Mike,John,Bill" çıktısı alert(oCar2.drivers); // "Mike,John,Bill" çıktısı
Yukarıdaki kodda, drivers özelliği Array nesnesine işaret eden bir işaretçidir, bu dizide "Mike" ve "John" iki adet isim bulunmaktadır. drivers referans değeri olduğundan, Car'ın iki örneği aynı dizeye işaret eder. Bu, oCar1.drivers'e "Bill" değeri eklenmesi durumunda, oCar2.drivers'te de görüleceği anlamına gelir. Bu iki işaretçenin herhangi birini çıktı alırsanız, "Mike,John,Bill" stringi görüntülenir.
Nesne oluşturma sırasında bu kadar çok sorun olduğundan, daha mantıklı bir nesne oluşturma yöntemi olup olmadığını düşünmek zorundasınız. Cevap evet, yapılandırıcı fonksiyon ve prototip yöntemlerinin birleşik kullanımı gereklidir.
Karışık yapılandırıcı fonksiyon/prototip yöntemi
Yapılandırıcı fonksiyon ve prototip yöntemlerinin birleşik kullanımı, diğer programlama dillerinde gibi nesneler oluşturmak için kullanılabilir. Bu kavram çok basittir, yani nesnenin tüm fonksiyon olmayan özelliklerini yapılandırıcı fonksiyonla tanımlayın, nesnenin fonksiyon özelliklerini (metotları) prototip yöntemiyle tanımlayın. Sonuç olarak, tüm fonksiyonlar yalnızca bir kez oluşturulur ve her nesne kendi nesne özellikleri örneğine sahiptir.
Önceki örneği yeniden yazdık, kod şu şekilde:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); } Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car("red",4,23); var oCar2 = new Car("blue",3,25); oCar1.drivers.push("Bill"); alert(oCar1.drivers); // "Mike,John,Bill" çıktısı alert(oCar2.drivers); // "Mike,John" çıktısı
Şu anda daha çok genel bir nesne oluşturmak gibi. Tüm fonksiyon olmayan özellikler yapılandırıcı fonksiyonda oluşturuluyor, bu da yapılandırıcı fonksiyon parametreleri ile öntanımlı değerler atayabileceğimiz anlamına gelir. showColor() fonksiyonunun bir örneği yalnızca oluşturulduğundan, bellek israfı yok. Ayrıca, oCar1'in drivers dizisine "Bill" değeri eklenmesi, oCar2'nin dizisini etkilemez, bu yüzden bu dizilerin değerlerini çıktı alırken, oCar1.drivers "Mike,John,Bill" olarak gösterilirken, oCar2.drivers "Mike,John" olarak gösterilir. Prototip yöntemi kullanıldığı için instanceof operatörünü kullanarak nesnenin türünü belirlemek hala mümkün.
Bu yöntem, ECMAScript'in ana yöntemidir ve diğer yöntemlerin özelliklerini taşır, ancak onların yan etkilerini taşımaz. Ancak, bazı geliştiriciler bu yöntemin mükemmel olmadığını düşünüyorlar.
Dinamik prototip yöntemi
Diğer dillerde çalışan geliştiriciler için, karışık yapıcı fonksiyon/prototip yöntemini kullanmak o kadar uyumlu değil. Çünkü sınıf tanımlarken, çoğu yönlü nesne dili, özellikleri ve yöntemleri görsel olarak sarmalar. Aşağıdaki Java sınıfını göz önünde bulundurun:
class Car { public String color = "blue"; public int doors = 4; public int mpg = 25; public Car(String color, int doors, int mpg) { this.color = color; this.doors = doors; this.mpg = mpg; } public void showColor() { System.out.println(color); } }
Java, Car sınıfının tüm özelliklerini ve yöntemlerini iyi bir şekilde paketlemiştir, bu yüzden bu kodun neyi gerçekleştireceğini bilmek kolaydır, bir nesnenin bilgilerini tanımlar. Karışık yapıcı fonksiyon/prototip yöntemini eleştirenler, özellikleri yapıcı fonksiyon içinde bulma ve yöntemleri dışında bulma işleminin mantıklı olmadığını düşünür. Bu nedenle, daha dostane bir kodlama tarzı sağlamak için dinamik prototip yöntemi tasarladılar.
Dinamik prototip yönteminin temel fikri, karışık yapıcı fonksiyon/prototip yöntemiyle aynıdır, yani yapıcı fonksiyon içinde fonksiyon olmayan özellikler tanımlanırken, fonksiyon özellikleri prototip özellikleriyle tanımlanır. Tek fark, nesneye yöntem atama konumudur. Aşağıda, Car sınıfını dinamik prototip yöntemiyle yeniden yazılmış olan:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); if (typeof Car._initialized == "undefined") { Car.prototype.showColor = function() { alert(this.color); }; Car._initialized = true; } }
Car._initialized typeof'ı "undefined" eşit olmadıkça bu yapıcı fonksiyon değişmez. Bu satır, dinamik prototip yöntemlerinin en önemli parçasıdır. Bu değer tanımlanmadıysa (değeri true olduğunda typeof'ın değeri Boolean), yapıcı fonksiyon prototip yöntemiyle nesne yöntemlerini tanımlamaya devam eder ve Car._initialized'i true olarak ayarlar. Bu değer tanımlanmışsa (değeri true olduğunda typeof'ın değeri Boolean), bu yöntem daha fazla oluşturulmaz. Kısacası, bu yöntem, prototipe herhangi bir yöntem atanıp atanmadığını belirlemek için bir işaret (flag) kullanır. Bu yöntem sadece bir kez oluşturulup atanır ve geleneksel OOP geliştiricileri, bu kodun diğer dillerdeki sınıf tanımlarına benzediğini görmekten memnuniyet duyar.
Karışık Fabrika Yöntemi
Bu yöntem, bir önceki yöntemi uygulamak mümkün olmadığında kullanılan bir alternatif yöntemdir. Amacı, sahte yapıcı fonksiyon oluşturmak ve sadece başka bir nesnenin yeni örneklerini dönmektir.
Bu kod, fabrika fonksiyonlarına çok benzer görünüyor:
function Car() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; }
Klasik yöntemle farklı olarak, bu yöntem new işlevi kullanır ve gerçek bir yapıcı fonksiyon gibi görünür:
var car = new Car();
Car() yapıcı fonksiyonu içinde new işlevi çağrıldığı için, yapıcı fonksiyon dışında bulunan ikinci new işlevi göz ardı edilir ve yapıcı fonksiyon içinde oluşturulan nesne, değişkene car geri gönderilir.
Bu yöntem, nesne yöntemlerinin iç yönetimi açısından klasik yöntemle aynı sorunları paylaşır. Çok güçlü bir tavsiyede bulunuyoruz: Mümkün olduğunca bu yöntemi kullanmayın.
Hangi Yöntemi Kullanmalı
Daha önce belirtildiği gibi, şu anda en yaygın olarak kullanılan karışık yapıcı fonksiyon/prototip yöntemidir. Ayrıca, dinamik temel yöntemler de popülerdir ve yapıcı fonksiyon/prototip yöntemleriyle aynı işlevselliktedir. Bu iki yöntemden herhangi birini kullanabilirsiniz. Ancak, klasik yapıcı fonksiyon veya prototip yöntemlerini tek başına kullanmayın, çünkü bu kodunuza sorunlar getirir.
Örnek
Nesnelerin ilginç bir yanı, onları çözüm bulma şeklidir. ECMAScript'te en yaygın sorunlardan biri dizgi birleştirme performansıdır. Diğer diller gibi, ECMAScript dizgileri sabittir, yani değerleri değiştirilemez. Aşağıdaki kodu göz önünde bulundurun:
var str = "hello "; str += "world";
Aslında, bu kodun arka planda gerçekleştirdiği adımlar şu şekildedir:
- "hello " dizgesini tutacak dizgeyi oluşturun.
- "world" dizgesini tutacak dizgeyi oluşturun.
- Birleştirme sonucunu tutacak dizgeyi oluşturun.
- str'nın mevcut içeriğini sonucuna kopyalayın.
- "world" dizgesini sonucuna kopyalayın.
- str'ı sonucu gösteren güncelleyin.
Her seferinde dizge birleştirme tamamlandığında adımlar 2'ye kadar 6'ya kadar çalıştırılır, bu nedenle bu tür bir işlem çok kaynak tüketir. Bu süreci yüzlerce, hatta binlerce kez tekrarladığınızda performans sorunlarına neden olabilir. Çözüm, dizgeleri Array nesnesinde saklamak ve ardından join() yöntemi (boş bir parametre) ile son dizgeyi oluşturmaktır. Daha önceki kodun yerine aşağıdaki kodu hayal edin:
var arr = new Array(); arr[0] = "hello "; arr[1] = "world"; var str = arr.join("");
Bu şekilde, dizgede kaç tane dizge girerse girsin bir sorun olmaz, çünkü sadece join() yöntemi çağrıldığında birleştirme işlemi gerçekleşir. Bu durumda, gerçekleştirilen adımlar şu şekildedir:
- Sonucu tutacak dizgeyi oluşturun
- Her dizgeyi sonucun uygun konumuna kopyalayın
Bu çözüm çok iyidir, ancak daha iyi bir yöntem de vardır. Sorun, bu kodun niyetini net bir şekilde yansıtmamasıdır. Bu işlevi daha anlaşılır hale getirmek için StringBuffer sınıfını kullanabilirsiniz:
function StringBuffer () { this._strings_ = new Array(); } StringBuffer.prototype.append = function(str) { this._strings_.push(str); }; StringBuffer.prototype.toString = function() { return this._strings_.join(""); };
Bu kodun dikkat edilmesi gereken ilk şey strings özelliğidir, bu özellik özeldir. Bu iki yönteme sahiptir, yani append() ve toString() yöntemleri. append() yöntemi bir parametreye sahiptir, bu parametre dizge dizisine eklenir, toString() yöntemi dizgenin join yöntemini çağırır ve gerçekten birleştirilmiş dizgeyi döndürür. StringBuffer nesnesi ile bir dizi dizgeyi birleştirmek için aşağıdaki kodu kullanabilirsiniz:
var buffer = new StringBuffer(); buffer.append("merhaba "); buffer.append("dünya"); var result = buffer.toString();
StringBuffer nesnesi ve geleneksel string birleştirme yöntemlerinin performansını test etmek için aşağıdaki kodu kullanabilirsiniz:
var d1 = new Date(); var str = ""; for (var i=0; i < 10000; i++) { str += "text"; } var d2 = new Date(); document.write("Ekleme işareti ile birleştirme: ") + (d2.getTime() - d1.getTime()) + " milliseconds"); var buffer = new StringBuffer(); d1 = new Date(); for (var i=0; i < 10000; i++) { buffer.append("text"); } var result = buffer.toString(); d2 = new Date(); document.write("<br />StringBuffer ile birleştirme: ") + (d2.getTime() - d1.getTime()) + " milliseconds");
Bu kod, string birleştirme işlemi için iki test yapıyor, birincisi ekleme işareti ile, ikincisi StringBuffer sınıfı ile. Her işlem 10000 adet string birleştiriyor. Tarih değerleri d1 ve d2, işlemi tamamlamak için gerekli süreyi belirlemek için kullanılıyor. Date nesnesi oluşturulduğunda parametre verilmezse, nesneye mevcut tarih ve zaman atanır. Birleştirme işleminin ne kadar süreceğini hesaplamak için, tarihlerin milisaniye değerlerini (getTime() yönteminin dönüş değeri) çıkarabilirsiniz. Bu, JavaScript performansını ölçmek için yaygın bir yöntemdir. Bu testin sonuçları, StringBuffer sınıfını kullanmanın ekleme işaretine göre ne kadar etkili olduğunu karşılaştırmak size yardımcı olabilir.
- Önceki Sayfa Nesne Etki Alanı
- Sonraki Sayfa Nesne Düzenleme