Your Ad Here

Javascript Yazılım Kuralları (JS)

22 Aralık 2007

Nesneler

Aşağıdaki işlev myobject sınıfından bir nesne yaratmak için gösterilmiştir.

function myobject() {
  this.containedValue = 0;
  this.othercontainedValue = 0;
  this.anothercontainedValue = 0;
}

var mything = new myobject();

İşte buradan devam edelim. mything, myobject sınıfından bir bileşendir. Aşağıdaki özellikleri vardır ve hepsinin değeri sıfırdır:

  • mything.containedValue
  • mything.othercontainedValue
  • mything.anothercontainedValue
Artık:
   myobject.prototype.newContainedValue = someValue;
yazabilirsiniz ve myobject sınıfının tüm bileşenlerinde "newContainedValue" değeri "someValue" olur.

Yöntemiyle bir nesne yaratmak

Şimdi nesneniz için nasıl yöntem tanımlanacağı gösterilmiştir. Örnek olarak, çember yaratmak nesne haline getirildiğinde:

nameOfCircle.retArea()
Çemberin alanını döndürür (pi r2)
nameOfCircle.retCirc()
Çemberin çevresini döndürür (2 pi r)
nameOfCircle.mvBy(xDis,yDis)
Çemberix yönünde xDis ve y yönünde yDis kadar hareket ettirir

Aşağıdaki satırlar nesnenin kullanacağı yöntemleri işlevlere bağlar

  • this.retArea = getTheArea;
  • this.mvBy = mvCclBy;
  • this.retCirc = function () { ... };

Bu tanımların üçüncüsü tek satırlık anonim bir işlevi belirtir. NN 4'de bu satır çalışmaz. '}' işaretinden sonra bir noktalı virgülü vardır ve bu da ender doğru örneklerden biridir.

function mycircle(x,y,r) {
  this.xcoord = x;
  this.ycoord = y;
  this.radius = r;
  this.retArea = getTheArea;
  // Aşağıdaki satır başka bir yazım kuralı kullanır
  this.retCirc = function () {
       return ( Math.PI * this.radius * 2 );
  };
  this.mvBy = mvCclBy;
}
function getTheArea() {
  return ( Math.PI * this.radius * this.radius );
}
function mvCclBy(xDis,yDis) {
  this.xcoord += xDis;
  this.ycoord += yDis;
}

/*
testcircle.xcoord 3 ve testcircle.ycoord 4 ve
testcircle.radius 5 olan testcircle adlı bir mycircle yaratmak 
*/
var testcircle = new mycircle(3,4,5);
/*
testcircle merkezini göstermek için mvBy yöntemini kullanın
x yönünde 2, y yönünde 3 kaydırın
*/
testcircle.mvBy(2,3);
// Şimdi testcircle.xcoord 5 ve testcircle.ycoord 7 olur

window.alert( 'The area of the circle is ' + testcircle.retArea() );
window.alert( 'The circumference is ' + testcircle.retCirc() );

Özel 'toString' yöntemi

Siz tanımlamamış olsanız da tüm nesnelerde 'toString' yöntemi vardır. Yöntem nesnenin yazı dizini biçimini döndürür. Bir nesnenin yazı dizini biçimindeki konumu istendiğinde otomatik çağrılır. Örneğin "alert(myObject)" işlemini kullandığınızda burada anlatılan gerçekleşir.

Pek çok tarayıcıda '[object Object]' döner ama bazı kullanışlı tarayıcılarda aşağıdakine benzer bir yazı görüntülenir:

  '{özellik1:değer1,özellik2:değer2,method1:function () { ... },etc.}'

Ancak, sizin nesneleriniz için daha kullanışlı bilgiler içeren yazı dizisi oluşturmak isterseniz 'toString' yöntemini yeniden yazmalısınız.

this.toString = function () {
  return 'Circle object; xcoord: ' + this.xcoord + ', ycoord: ' +
    this.ycoord + ', radius: ' + this.radius;
};

Gelişmiş Nesne Teknikleri

Burada Javascript ile çok az kullanılan bir kavramdan söz edilmektedir. Javascript herkesin kullandığından daha güçlü bir programlama dilidir. Bunun nedeni normal kodlamada bu özelliklerin hepsinin kullanılması gerekmemektedir.

NN 4 gibi eski tarayıcılarda burada anlatılanların bir çoğu çalışmayabilir.

"prototype" kullanarak daha çok özellikler/yöntemler ekleme

Bu yazının başındaki "mycircle" örneğini ele alın. Şimdiye dek 'mycircle' sınıfının bir bileşeni olarak 'testcircle' yaratıldı. Biraz daha 'mycircle' tanımları yaratıldığını varsayalım. Şimdi her çembere 'texture' gibi yeni bir özellik daha eklenmek istendiğinde aşağıdaki kullanılabilir:

testcircle.texture = 'smooth';

Ve bunu her 'mycircle' bileşeni için yapabiliriz. Eğer bu özellik tüm bileşenlere eklenecekse, ayrı ayrı tanım yapmak yerine hepsini birden bir kerede tanımlamak çok daha iyidir:

mycircle.prototype.texture = 'smooth';

Artık tüm 'mycircle' bileşenlerinde yeni özellik eklenmiştir.

alert(testcircle.texture);
//alerts 'smooth'

Yeni yöntemler de aynı biçimde eklenebilir.

mycircle.prototype.setArea = function (oArea) {
  this.radius = Math.sqrt( oArea / Math.PI );
};
mycircle.setArea(5);

Yerleşik (içsel) nesneler için daha kullanışlıdır. Örneğin /a[0-9]b/g new RegExp('a[0-9]b','g') yerine kullanılan bir kısaltmadır. Aslında bu işlem tüm yerleşik nesnel sınıfların (String, Number ve Boolean gibi) hepsi için geçerlidir. Tüm yazı dizilerinde yazı dizisinin içeriğini ters çeviren yeni bir yöntem tanımlanmak istediğinde (reverse gibi) aşağıdaki işlem yapılır:

String.prototype.reverse = function() {
  for( var oStr = '', x = this.length - 1, oTmp;
       oTmp = this.charAt(x); x-- ) {
    oStr += oTmp;
  }
  return oStr;
};

'prototype', 'mycircle' nesnesinin tüm yöntemleri için de kullanılabilir. Bu özellik yalnız yeni olanlar için geçerli değildir. Nesnenin her bileşeni için ayrı bir kopya oluşturmak gerekmez. Ama tarayıcı yöntemin o anki ve önceki kapsamını aramak zorundadır. Az yer alır ama biraz daha uzun sürer. Genel olarak kodunuza en uygun hangisiyse onu kullanmanız önerilir. Ve bu kararı verimliliği düşünerek vermeniz gerekmez (ama çok özel denetim ortamıyla lgileniyorsanız daha hassas davranabilirsiniz).

function mycircle(x,y,r) {
  this.xcoord = x;
  this.ycoord = y;
  this.radius = r;
}
mycircle.prototype.retArea = function () {
  return ( Math.PI * this.radius * this.radius );
};
mycircle.prototype.retCirc = function () {
  return ( Math.PI * this.radius * 2 );
};
mycircle.prototype.mvBy = function (xDis,yDis) {
  this.xcoord += xDis;
  this.ycoord += yDis;
};

Toplumsal (Public) ve kişisel (private) özellikler

Bu öyle bir kavramdır ki neredeyse Javascript kodlamasında hiç kullanılmaz. Bunun için geçerli bir neden de vardır. Basit olarak buna gerek yoktur. Çok karmaşık kodlar bile bu işlemi kullanmaya gereksinim duymaz. Ama Java ve C++ gibi diğer dilleri kullanan programcılar Javascript içinde bu davranışı sıkça kullanmak isterler. Bu yöntem büyük bir projede birçok kod parçasından oluşan kaynak kod için yararlı olur.

Diyelim ki siz toplumsal işlev kitaplığı oluşturuyorsunuz. Bunlar bir dizi yapılar ve yöntemlerdir. Öyle ki herkes projelerinde onları kullanabilir (Java programcılarının her zaman kullandıkları bir şey). Diyelim "mycircle" yapısı da bunların içindedir. Böylece siz kendi "mycircle" yapınızı kurabilirisiniz. Aşağıdakini yazmış olun:

   var aCircle = new mycircle(5,6,'about 3mm');

Bu çalışır (doğru ölçü değil belki ancak yapı tanımı bundan etkilenmez), ama sonra bu sınıfla desteklenen yöntemleri kullanmaya kalktığınızda hepsi çakılır. O halde bu değerin geçerli olduğunu denetlemiş olmalısınız ve eğer değilse varsayılan değeri kullanmış olmalısınız. Ama eğer aşağıdaki gibi birşeyden söz ederseniz:

   aCircle.radius = 'some text';

Yine kırılacaktır (Doğal olarak sizin hatanızdan, ama daha karmaşık uygulamalarda, bu biçimde hatalar yapma olasılığı vardır ve bunlar da gerçekten hata oluştururlar). O halde özelliği doğrudan değiştirme olanağının kaldırılması gerekir. Bunu ancak denetlenebilen bir yöntem aracılığıyla yapmaya izin verilmelidir:

this.setRadius = function (oRad) {
  if( typeof(oRad) == 'number' && oRad >= 0 ) {
    this.radius = oRad;
  } else {
    this.radius = 0;
  }
};

Önemli olabilecek diğer bir durum bir dizi özelliğe bu bilginin saklanmasıdır. Ama bunu gerçekleştirmek için kullanılan özelliklerin değişmesi gerekir. Kullanıcı yalnız yöntemleri kullanmaya zorlanır. Ve yöntemler yeni özellikleri kapsayacak biçimde değiştirilse bile yazılmış olanlar değişmeden kullanılabilir. Bu durumda neyin değiştiğinin izlenmesine bile gerek olmaz. Böylece herkes kendi projesini geliştirebilir ama birbirlerinin zamanını işgal etmemiş olurlar.

Bunlar kapanmamış bir çok özellikte karşılaşılacak çelişkileri yok etmeye de yarar. Örneğin bir özellik bir nesneye gecici olarak atanmak istense, içeriği çok da iyi bilinmiyorsa ve eğer bu özellik daha önce zaten kullanılmışsa, yenisi eskinin yerini alacak ve nesne üzerinde sorunlara neden olacaktır.

Burada kişisel özellikler devreye girer. Özelliklerin doğrudan değiştirilmesine gerek olmadan yapıları kullanan kodların yöntemler üzerinde değişiklik yapmalarını sağlar. Pek çok bilgisayar programlama dillerinden ayrılarak Javascript değişkenlerin 'public' ya da 'private' tanımına gerek duymaz. Bu tümüyle onların nasıl yaratıldığına bağımlıdır.

'this.propertyname' yukarıda belirtildiği gibi toplumsal özellik yaratır. Her kod bir nesne yaratabilir ve özelliklerini doğrudan değiştirebilir. Değişkeni tanımlamak için yapıda "var" kullanmak "kişisel" özellik yaratılmasını sağlar. Toplumsal özelliklere karşın, kişisel özellikler sonra erişimde (yöntemlerin içinde) "this" ön kodu olmadan normal değişken gibi değerlendirilir. Bu javascript kapsamının işlevsel olarak yoğun kullanımını gerektirir. Kişisel değişkenler satır içinde tanımlanmışsa, yalnız yöntemlerde erişilebilir ve dışarıdan erişilemez ya da prototype yapısıyla tanımlanamaz. Buna örnek:

   this.mymethod = function () { alert( propertyname ); };
olur...
function myob() {
  this.property1 = 'value1'; //this creates a public property
  var property2 = 'value2';  //this creates a private property
  this.method1 = function () { alert( property2 ); };
}
var oneOb = new myob();
alert(oneOb.property1); //alerts 'value1'
alert(oneOb.property2); //alerts undefined (private property)
oneOb.method1();        //alerts 'value2'

Benzer biçimde kişisel yöntemler de tanımlanabilir. yapının içinde tanımlanmış işlevler bu tür tanımlardır. Biraz karışık görünüyor olabilir ama çalışır. Kişisel işlev yalnız kendi yapısı tarafından çağrılabilir. Eğer bunlar toplumsal yöntem yapıları içinde tanımlanırlarsa ve toplumsal yöntemlerle erişilirlerse (aşağıdaki "method2" de olduğu gibi) kişisel yöntemler olarak tanımlanabilirler.

function myob() {
  function cantBeSeen() {
    alert(secretValue);
  }
  var secretValue = '';
  this.method1 = function () {
    secretValue = 'no surprises';
    cantBeSeen();
  };
  this.method2 = cantBeSeen;
}
var oneOb = new myob();
oneOb.method1(); //alert mesajı 'no surprises' olur
oneOb.method2(); //alert mesajı 'no surprises' olur

Bu bölüm de Javascript ile pek kullanılmaz. Başka dillerde çok yaygın olması burada da kullanılmasını gerektirmez. Küreyi tanımlayacak verilerle yeni tür bir nesne yaratılacaktır. Aslında küre üç boyutlu bir çember olduğundan daha önce çember için yaratılmış olan nesnelerin hepsi kullanılmak istenecektir. retArea kesit alanını elde etmek, retCirc çevresini almak, mvBy belirli bir tür hareket ettirmek, ek olarak retVol hacmini elde etmek ve retSurf yüzey alanını almak için kullanılacaktır. Nesneyi yaratırken ya da mvBy kullanırken z koordinatına da gereksinim doğacaktır.

Böylece 'mycircle' nesnesine dayanan yeni bir nesne, birkaç ekle ya da değişiklikle yaratılacaktır. Şimdi 'mycircle' için yazılmış tüm kod yeniden yazılır ve yalnız gereken değişiklikler üzerinde uygulanır. Gerçek yaşamda bu zaman kaybından başka birşey değildir. Burada yapılması gereken kürenin kalıtsal olarak 'mycircle' özelliklerinin hepsini içermesi ve yalnız değişenlerin üzerine yazılmasıdır.

Bu mysphere nesnesini etkin olarak 'mycircle' nesnesinin alt-sınıfı yapar. Böylece, kalıtsal olarak mycircle'dan sınıf yaratılmasını sağlar. Bu işlem aşağıdaki kadar basittir:

function mysphere(x,y,z,r) { ... yapıcı kodlama ... }
mysphere.prototype = new mycircle();

Nasıl çalıştığı merak konusu oluyorsa: Önce mycircle yaratılır ve sonra mysphere yapısına prototype olarak atanır. Sonuç olarak mysphere yapı tasarımı, mycircle nesnesini ilk tip (prototip) zincirine eklemiş olur. Bunun anlamı her mysphere yaratıldığında, mycircle nesnesinin yöntem ve özelliklerini beraberinde taşır. Kendisi ilk tipi kullanarak bu özelliklerden herhangi birini değiştirebilir. Eğer bu özelliklerden herhangi biri "delete" (sil) kullanarak silinirse katılsal olanlar yeniden açılmış olurlar. Zincir içindeki kopyalar değişikliklere göre elden geçer ve değişiklikler kendilerine uygulanır.

"mycircle" nesnesinden kalıtsal bağ alarak mysphere oluşturmak oldukça kolaydır. En azından öyle olmalıdır. Ama unutulmaması gereken mycircle, bazı parametreler kullanılmasını beklemektedir. Yeni yapı tasarımcısı kullanıldığında bu parametreler otomatik olarak mycircle işlev yapısına geçecektir. Bunu, yaratılmak istenen her mysphere için yapmak gerekir. Ama parametrelerin ilk tip ataması sırasında mycircle nesnesine geçirilmemiş olması, mycircle yapısının yaratılmadan durmasına neden olur. Toplumsal parametreler hemen tanımlanmadan bir yöntem tanımlayarak işlem yapılabilir. Eğer gerekli parametreler (arguments.length) geçirilirse yöntem hemen çağrılır.

Eğer parametreler varsa, mycircle yapı tasarımı çalışmaya devam eder ve hiç kimsenin değişiklikleri bilmesi de gerekmez. Daha sonrası için işleri kolaylaştırmak amacıyla, yapı tasarımlarından tüm yöntemler dışarı çıkarılır ve ilk tipler kullanılırken eklenir. Bu biçimiyle mycircle ilk tip yöntemleri her zaman kullanılabilir olur. Maalesef toplumsal ve kişisel özelliklerin kullanımı imkansız olur.

function mycircle(x,y,r) {
  if( arguments.length ) { this.getready(x,y,r); }
}
mycircle.prototype.getready = function (a,b,c) {
  this.xcoord = a;
  this.ycoord = b;
  this.radius = c;
};
mycircle.prototype.retArea = function () {
  return ( Math.PI * this.radius * this.radius );
};
mycircle.prototype.retCirc = function () {
  return ( Math.PI * this.radius * 2 );
};
mycircle.prototype.mvBy = function (xDis,yDis) {
  this.xcoord += xDis; this.ycoord += yDis;
};

Yeniden mysphere yapı tasarımına dönüldüğünde onun nasıl kalıtsal olduğu anlatılmıştır ama hala mycircle.getready yöntemi ilkleme için gereklidir. Bunu yapmak için üst katmandaki sınır ilk tipini göstererek yöntemi çalıştırmak gerekir. Üst düzey yöntem aynı adı taşısa da her zaman doğru olan kullanılır. Ne kadar alt-sınıf gerekeceği hiç bir zaman bilinmese de, yukarıda anlatıldığı biçimde olunca ad çatışması olmaz.

Yöntemi çalıştırırken, javascript'e başka bir ilk tip yöntemi olduğu belirtilse bile onun yaratılan nesnenin yöntemiymiş gibi kullanması istenebilir (Yaratılmakta olan nesneye eklenen özellikler olması koşulu gereklidir). Bu "call" ya da "apply" yöntemiyle yapılabilir fakat, IE 5 bunları anlamamaktadır. Bu nedenle yeni nesnenin gecici özellikleri atanır ve buradan çalıştırmak zorunda kalınır.

"mycircle" nesnesi mysphere ilk tipine atanınca, bu işlem mysphere ilk tip yapı tasarımındaki özellikleri ve değiştirir. Bu çok önemli bir sorun olmaz ama, bazı kodlamalarda kullanılmaktadır. Bu nedenle gösterilimi (reference) nerede olması gerekiyorsa oraya geri koymak gerekir.

function mysphere(x,y,z,r) {
  if( arguments.length ) { this.getready(x,y,z,r); }
}
//mycircle ilk tipinden alındı
mysphere.prototype = new mycircle();
//doğru yapı tasarımını geri koymak (zorunlu değil)
mysphere.prototype.constructor = mysphere;

mysphere.prototype.getready = function (a,b,c,d) {
  //üst sınıftan getready yöntemini almak
  this.tempReady = mycircle.prototype.getready;
  //ve bu nesnenin bir parçasıymış gibi çalıştırmak
  this.tempReady(a,b,d);
  //Bilinmelidir ki tüm zorunlu özellikler üst sınıftan
  //alıntıdır, başkaları ise buradan tanımlanır.
  this.zcoord = c;
}
mysphere.prototype.mvBy = function (xDis,yDis,zDis) {
  //Var olan yöntemi ezmek/değiştirmek
  this.xcoord += xDis;
  this.ycoord += yDis;
  this.zcoord += zDis;
};
mysphere.prototype.retVol = function () {
  return ( 4 / 3 ) * Math.PI * Math.pow( this.radius, 3 );
};
mysphere.prototype.retSurf = function () {
  return 4 * Math.PI * this.radius * this.radius;
};
Ve sonunda kullanmak için:
var testsphere = new mysphere(3,4,5,6);

alert( 'The cross-section area is ' + testsphere.retArea() );
alert( 'The circumference is ' + testsphere.retCirc() );
alert( 'The volume is ' + testsphere.retVol() );
alert( 'The surface area is ' + testsphere.retSurf() );