Derinlemesine Javascript Prototypes

C#,Java,C++ gibi nesneye yönelimli dillerde yeni bir nesne(object) oluşturmak için temel olarak nitelik ve metotlara sahip bir sınıf(class) oluşturulur.  Sınıf oluşturulduktan sonra sınıf örneğini(instance) new anahtar kelimesi ile örnekleriz. Örneğin C# dilinde bir sınıf tanımladığımızı düşünelim. Oluşturulan sınıf aslında gizlenmiş Object sınıfından türer ve Object sınıfına ait önceden tanımlanmış(toString(), Equals(),GetHashCode()) metotlara kalıtım ile erişebiliyoruz. Javascript te ise sınıf yoktur. Sadece object(nesne) ve primitive data type(temel veri tipleri) leri mevcuttur. Object literal, constructor function(kurucu fonksiyon) ve singleton function kullanarak nesne oluşturulabilir.

 

 

Yukarıda görüldüğü gibi Javascript te nesne tanımlamanın farklı türleri vardır. Görüldüğü gibi nesne oluşturmak için sınıf kullanmıyoruz. Bundan dolayı Javascript sınıf tabanlı değil prototype tabanlı bir dildir. Nesne oluşturulduktan sonra nesnenin örneği ile kendi içinde tanımlanmış özelliklerine ve metotlarına ulaşıp kullanabiliyoruz. Ama 3. nesne tanımlama örneğinde nesnenin(nesne3), bizim tarafımızdan tanımlanmamış hasOwnProperty() fonksiyonuna da eriştiğini görüyoruz. (Aynı özellik diğer nesne tanımlamalarında da kullanılabilir). Acaba bu fonksiyon nesnesinin bu fonksiyonu nereden geldi? İşte gizlenmiş bu fonksiyon(metot) Javascript Prototype dan gelir. Aslında burada kalıtım yolu ile önceden tanımlanmış bu metodu miras aldık.

Prototype Nedir?

Öncelikle şunu belirteyim. C#,Java gibi yazılım dili kullananlar için burada bahsedeceğim konular kafa karışıklıklarına neden olabilir. Bundan dolayı teorik bilgileri tekrarlı bir şekilde örneklerle destekleyeceğiz.

Javascript te ki her nesne(Object, Array, Function…) içinde prototype olarak bilinen bir property(özellik) sahiptir. Bu özellik aslında bir nesnedir. Bütün javascript nesneleri, kendi prototype nesne içindeki özellik ve metotları miras alır. Object, Array ve Function temel nesne tiplerinin prototype özelliklerini inceleyelim.

Object.prototype özelliği, temel Object nesnesinin prototype nesnesini temsil eder. Javascript teki bütün nesneler Object.prototype nesnesinden türer. Böylece bütün nesneler Object.prototype içinde bulunan özellikleri ve metotları miras alır.

Array.prototype özelliği, Array constructor için prototype temsil eder. Array nesne örnekleri(instance) Array.prototype miras alır.

Function.prototype özelliği, Function nesnesinin prototype nesnesini temsil eder. Yani Function nesneleri Function.prototype özelliğinden türerler.

Örneğin aşağıda tanımlanmış bir constructor function tipindeki fonksiyonun object(nesne) tipinde prototype özelliğine sahip olduğunu görüyoruz.

Aynı şekilde aşağıda görüldüğü gibi oluşturduğumuz myFunction fonksiyon nesnesinin örneğinde birbirine bağlı zincirlemeli bir prototype nesnelerinin oluştuğunu görüyoruz. (__proto__ özelliği ile prototype nesne özelliklerine erişilmiştir.)

JavascriptProtoypes2

Oluşturduğumuz fonksiyonun tipi function tipindedir. Function Javascriptte tanımlanmış bir nesnedir. Kendisine ait metotlara ve özelliklere sahiptir. Örneğin, en sık kullanılan metotları apply() ve call() metotlarıdır. Ayrıca en sık kullanılan özellikleri length ve consturctor özelliğidir. Function nesnesinin başka en önemli özelliği prototype özelliğidir. Peki bu prototype özelliği tam olarak nedir?

ProtoType

 

Örneğin yukarıdaki verdiğimiz fonksiyona bakalım. Fonksiyon tanımlandıktan sonra fonksiyonun prototype özelliğini console da yazdıralım.

JavascriptProtoypes1

Görüldüğü gibi fonksiyon tanımlandığı anda prototype özelliği otomatik olarak boş bir nesne oluşturur. Bu prototype özelliğinin içerisine tarafımızdan eklemek isteğimiz özellik ve metotları ekleyebiliriz. Böylece kalıtımı işletmiş oluruz.  Hadi bir örnek yapalım.

Yukarıdaki kodu incelediğimizde fonksiyonun prototype özelliğini yazdir() metodunu ekledik.  Artık myFunction tipinde yeni oluşturduğumuz tüm nesne örnekleri ortak paylaşıma açık yazdir() metodunu kullanabileceklerdir.  apply() metodu nasıl Javascript Function nesnesi içerisinde önceden tanımlanmış bir metot ise yazdir() metodu da myFunction  fonksiyon nesnesi içinde prototype tipinde tanımlanmış bir metot haline getirdik. Şimdi tanımladığımız fonksiyon üzerinden başka bir örnek verelim.

Oluşturduğumuz fonksiyon sadece ad argümanına sahiptir. Fonksiyon nesnesinden yeni bir nesne örneği tanımlıyoruz. Bizim tarafımızdan tanımlı yazdir() metoduna erişebiliyoruz. Fakat görünürde toString() metodunu myFunction için biz tanımlamadık. Nasıl oldu da fonksiyon nesnesi bizim tarafımızdan tanımlanmamış toString() metoduna erişti.  İşte olayın püf noktası burada.

ProtoType

Javascriptte fonksiyon tanımlandığı anda veya yeni bir nesne oluşturduğumuzda kendi prototype boş nesne özelliğini kendisine eklediğini söylemiştik. Bir fonksiyon nesnesi üzerinden bir özellik çağırdığımızda öncelikle kendi içerisinde çağırdığımız bu özelliğin olup olmadığına bakar.(myFunction da sadece “ad” özelliği vardır.) Eğer bulamazsa fonksiyonun prototype nesne özelliği içerisinde bu özelliği arar.  Bu arama işlemi özellik bulunana kadar ya da prototype özelliği null olana kadar devam eder. Yani özellik veya metot arama işlemi prototype chain olarak bilinen zincirleme şeklide olur. Peki prototype özelliği içerisinde tanımlanmış özellik veya metotlara nasıl erişir. Bunu standart olmayan __proto__ özelliği ile erişir. __proto__ özelliği tarayıcılarda uyumluluk sorunları olduğundan dolayı kullanılmamalıdır.

Şimdi toString() özelliğini ararken hangi aşamalardan geçtiğini görelim. Bunun için Javascript te tanımlanmış, tüm nesnelerin sahip olduğu hasOwnProperty() fonksiyonunu kullanacağız. Bu özellik ile bir nesnenin kendi içerisinde özellik tanımlı ise true, tanımlı değilse false geriye döndür.

Öncelikle myFunction için nesne adında bir örnek tanımlamıştık. Bu nesne örneği erişmek istediğimiz toString() özelliğini ilk önce kendi içerisinde arar.  Yukarıda görüldüğü gibi bu özelliği bulamadığından dolayı false olarak geriye döndürdü.(Çünkü sadece “ad”özelliği tanımlıdır.) Daha sonra bu özelliği kendi prototype özelliğinde arar.  toString() özelliğini bulamayıp yine false geriye döndü.(Çünkü myFunction prototype özelliği içerisinde sadece “yazdir()” metodu tanımlıdır.) Daha sonra fonksiyon nesnesi zincirlemeli bağlantı şeklide bu özelliği Javascript içerisinde tanımlı Object nesnesinde arar. toString() özelliği Object nesnesi içerisinde tanımlı olduğu için true değerini geriye döndürür. Dikkat edildiği gibi prototype özelliği içerisinde özelliği ararken __proto__ özelliğini kullandık. Peki __proro__ özelliği nesne içerisinde nasıl davranır?

myFunction nesnesi tanımlandığı anda __proto__ özelliğini oluşturur. Bu özelliği Function.prototype özelliğine yerleştirir. Böylece bir myFunction bir özelliğe ulaşmak istediği zaman ilk önce kendi içerisinde tanımlı olup olmadığına bakar. Eğer yok ise __proto__ link özelliği ile zincirlemeli bir şekilde özelliği bulana kadar ya da prototype özelliği null olana kadar aramaya devam eder. Aşağıda görüldüğü gibi tanımladığımız myFunction nesnesi Function.prototype özelliğinden türediğini görüyoruz.

 Niçin Prototype Özelliğini Kullanmalıyız?

Bilindiği gibi Javascript nesneye ait özellik oluştururken anahtar/değer(key/value) şeklinde özellik eklemesi yapıyoruz. Şimdi örnek bir senaryo ile durumu izah edelim. Araba üreten bir fabrikamız olduğunu düşünelim. Temel olarak araba nesnelerini aşağıdaki gibi object literal kullanarak tanımlayabiliriz.

Düşünsenize bunun gibi 50 adet araba nesnesi ürettiğimizi düşünün. Her ürettiğimiz bu araba nesneleri bellekte ayrı ayrı tutulacak. Doğal olarak tarayıcı üzerinde zamanla bellekte doluluktan dolayı yavaşlamalar meydana gelecektir. Bu da istemediğimiz bir durumdur. Ayrıca alt alta 50 tane araba nesnesi tanımlamak geliştirici için zaman kaybına neden olacaktır. Yine aynı şekilde nesneler üzerinde bir değişiklik yapmak istediğimiz zaman hepsi için ayrı ayrı değişiklik yapmamız gerekecektir. İşte bu yaşanan sıkıntıları bir nebzede olsa constructor function nesnesi tanımlayarak çözebiliriz. Şimdi yeniden araba nesnesini tanımlayalım.

Görüldüğü gibi araba nesnesini constructor function tipinde tanımladık. Şimdi arabaObject nesnesine uret metodunu ekleyebiliriz.   Fakat uret() metodu dikkat edildiyse tüm nesne örnekleri için aynı çalışacak.

Şimdi 50 adet araba nesnesi oluşturalım ve bir dizide tutalım. Daha sonra araba üretimine başlayalım.

Yukarıda görüldüğü gibi 50 adet araba nesnesi oluşturup bir dizide tuttuk. Daha sonra araba üretimini yapmak için her nesne için uret() metodunu çağırdık. Dikkat ettiyseniz her nesne için uret metodu ayrı ayrı oluşturuluyor. Bu ileriki zamanlarda bellek yetersizliklerine neden olup tarayıcı üzerinden cevap alınamadı hatalarına neden olacaktır. Ayrıca uygulama çalışma anında iken uret metodu override edilerek bazı değişikliklere uğramasını isteyebiliriz. Bunun için uret metodu çağırılmadan hemen önce her nesne için uret metodunu yenilemeliyiz. Buradaki dezavantaj her nesne için uret metodunu döngü içinde değiştirmek.

Bunun gibi sıkıntılar bilindiği gibi C#,Java gibi sınıf tabanlı nesneye yönelimli dillerde kalıtım ile çözülür. İşte Javascriptte bize kalıtım özelliğini prototype sağlar. Hemen araba üretimini ptototype tabanlı değiştirelim.

Yukarıda görüldüğü gibi arabaObject nesnesinin prototype özelliğine uret metodunu ekleyerek, tüm arabaObject nesne örnekleri üretim için artık ortak paylaşılan uret metodunu kullanacaklardır. Zamanla araba üretiminin yapısı değişebilir.

Yukarıdaki gibi uret metodu override edilerek değiştirilebilir. Dikkat etmeniz gereken bir kısım var. uret metodu override edilmeden önce 50 adet eski uret metodunu kullanan nesne üretmiştik. Ama artık uret metodu değiştiğinden dolayı geçmişte oluşturulan tüm nesne örnekleri yeni uret metodunu kullanacaklardır. Yani yukarıdaki işlemin soncunda 50 adet,

Araba 1 yeni model araba üretildi.

Araba 2 yeni model araba üretildi.

.

.

.

şeklinde console da sonuç çıkacaktır. Çünkü prototype içinde bulunan özellikler(uret metodu), tüm nesne örnekleri için ortak paylaşılır.

Görüşmek üzere…

 

 

 

IEnumerable ve IQueryable Arasındaki Farklar Nelerdir?

Merhaba,

IEnumerable ve IQueryable  çoğu geliştirici tarafından kullanımı karıştırılmakta ve ne zaman nerede kullanılacağı bilinmemektedir.  Kod yazım tipleri birbirine çok benzerdir. Fakat kullanım yerleri işlevselliklerine göre değişir. Yanlış kullanımlar sistem performansını oldukça kötü etkiler.

IEnumerable ve IQueryable aradındaki farkı anlatmadan önce kısaca bu iki interface sınıfını tanıyalım.

IEnumerable, generic olamayan bir koleksiyon üzerinde iterasyon(liste içerisinde dönmemizi) yapmamızı sağlar. Aslında daha çok memory(bellek) üzerinde muhafıza edilen veriler üzerinden gerekli sorgulama işlemleri yapar. .Net Framework altında System.Collection isim uzayından türemektedir. IEnumerable sınıfı içerisinde sadece GetEnumerator metodu bulundurmaktadır. GetEnumerator metodu bir koleksiyon dizi içerinden iterasyon yapmamızı sağlayarak  geriye IEnumerator tipinde bir sınıf döndürmektedir. Aslında bir çok yerde kullandığımız foreach döngüsü IEnumrable sınıfı üzerinden GetEnumerator işlenerek çalışmaktadır.

IQueryable, belli bir uzak veri kaynağından(web service,database…) verileri sorgulamak için işlevsellik sağlar. .Net Framework altında System.Linq isim uzayından türemektedir. IQueryable arayüzü IEnumerable arayüzünü implement etmektedir. Demek ki IEnumerable ait tüm özelliklerine sahip olabilecek.

resim1

 

Şimdi  basit bir uygulama ile yaptıkları işlere bakalım. Uygulamamız basit bir Console Application projesi ile yapacağız. Veritabanına bağlanıp sorgulama işlemi yapacağımızdan dolayı entity framework orm kullanacağız.

Solution

Öncellikle aşağıda class diagramı göründüğü gibi basit Employee POCO(Plain Old Clr Object) tipi oluşturduk.Employee sınıfı tablomuza karşılık gelen bir modeldir.

ClassDiagram

 

Entity Framework ile gelen DbContext nesnesi üzerinden veritabanımızdaki employee tablosuna erişerek gerekli verileri IEnumerable ve IQueryable ‘dan türeyen DbSet<TEntity> generic nesnesine aktararak veriye ulaşırız .

 

Veritabanına bağlanıp employee tablosuna eriştiğimize göre şimdi verileri alıp önce IEnumerable üzerinden bakalım.

DbContext nesnesi üzerinden veritabanına bağlantı kurarak tüm Employee listesini aldık. Employee listesi üzerinden görüldüğü gibi arka arkaya 2 filtreleme işlemi yaptık.

 

Şimdi adım adım işlemleri takip edelim. Sql Profiler tool açarak çalışma anında veritabanına giden sorguyu tespit edelim. Burada veritabanından tüm employee listesi ilk önce alındı. Alınan bu veri IEnumerable sayesinde memory(bellekte) muhafaza edilir. Daha sonra üzerinde sorgulama işlemi yaptığımız employeeListFilter1 ve employeeListFilter2 işlemleri Linq to Object düzeyinde yapılır. Yani sorgulama işlemi memory muhafaza edilen nesneler(object) üzerinden yapılır.

IEnumerable_Sql_Profiler

 

Şimdi aynı duruma IQueryable düzeyinde bakalım.

 

IQuarable_Sql_Profiler

Buradaki işlemlere bakarsak işleyiş şöyle devam eder. Sırayla 2 adet filtreleme işlemleri için tek bir Sql sorgusu oluşturulur. Bu sorgu Linq to Sql düzeyinde oluşturulur. Yani sorgulama direkt veritabanı(uzak veri kaynağı) üzerinden yapılır.

IEnumerable ve IQueryable arasındaki temel fark, IEnumerable tüm verileri alıp memory de tutarak, sorgulama işlemlerini memory üzerinden yaparken IQueryable ise şartlara bağlı query oluşturarak doğrudan veritabanı üzerinden sorgulama işlemi yapar. Eğer milyonlarca kayıt üzerinde sorgulama işlemi yapıyorsak elbette IQueryable IEnumerable göre daha hızlı sorgulama işlemi yapar.

IEnumerable lazy loading özelliğini desteklemez. IQueryable lazy loading özelliğini destekler. Bilindiği gibi lazy loading ile nesneye ihtiyaç duyulduğu zaman veri kaynağından yükleme yapılır. Örneğin, DataGrid üzerinde milyonlarca kayıtı sayfaladığmızı(Paging) düşünelim. Her sayfa için veritabanından yapılan sorgulama oldukça hızlı olacaktır.

Görüşmek üzere…