Bu bölümde prototipler ile çalışmak için ek metodlardan bahsedeceğiz
Bizim bildiğimizin haricinde prototipi ayarlamak ve almak için başka yöntemler de bulunmaktadır:
- Object.create(proto[, descriptors]) – verilen
proto’yu[[Prototype]]şeklinde alarak ve opsiyonel tanımlayıcı özelliği kullanarak boş bir obje oluşturur. - Object.getPrototypeOf(obj) –
obj’nin[[Prototype]]'ını döndürür. - Object.setPrototypeOf(obj, proto) –
obj’nin[[Prototype]]'ınıproto’ya ayarlar.
Örneğin:
let animal = {
eats: true
};
// animal prototipi ile yeni bir rabbit objesi yaratma.
let rabbit = Object.create(animal);
alert(rabbit.eats); // true
alert(Object.getPrototypeOf(rabbit) === animal); // rabbit'in prototipini alma
Object.setPrototypeOf(rabbit, {}); // rabbit'in prototipini {}'e çevirme.
Object.create opsiyonel olarak ikinci bir argümana sahiptir: özellik tanımlayıcı. Aşağıdaki gibi yeni objeye özellikler ekleyebiliriz:
let animal = {
eats: true
};
let rabbit = Object.create(animal, {
jumps: {
value: true
}
});
alert(rabbit.jumps); // true
Tanımlayıcıların Özellik bayrakları ve tanımlayıcılar bölümünde üstünden geçilmiştir. Formatları aynıdır.
Object.create kullanarak obje klonlama işlemini yapabiliriz. Bu objenin özelliklerini for..in ile dolanmaktan daha etkin bir yöntemdir.
// Objenin yüzeysel klonu
let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
Bu tam olarak obj’nin aynısını verir. Tüm özellikler: dönülebilir veya dönülemez, veri özellikleri, alıcı ve ayarlayıcılar – her şey, ayrıca doğru [[Prototype]] ile
Tarihçe
[[Prototype]]'ı ayarlayabileceğimiz yöntemleri saymaya kalsak baya zorluk yaşarız. Çok fazla yöntem bulunmaktadır.
Neden?
Birçok nedeni bulunmaktadır.
- Yapıcının
"prototype"özelliği ilk javascript ortaya çıktığından beri bulunmaktadır. - 2012’de:
Object.createbir standart olarak oturdu. Bu verilen prototip ile objeleri yaratmaya izin verdi. Fakat bunları almaya veya ayarlamaya değil. Bundan dolayı tarayıcılar bir standart olmayan__proto__erişimcilerini uygulayarak alıcı ve ayarlayıcılara ( get/set)'e izin verdi. - 2015’te:
Object.setPrototypeOfveObject.getPrototypeOfbir standart olarak eklendi.__proto__defakto şeklinde aslında her yerde kullanılmıştı, Bundan dolayı çokta kulllanılan özellikler olmadı, sadece tarayıcı harici çevrelerde kullanılır oldu.
Artık bunların hepsi bizim kullanımımızdadır.
Teknik olarak [[Prototype]]'ı istediğimiz an alma/ayarlama işi yapabiliriz. Fakat genelde bunu sadece obje yaratırken kullanır ve daha sonra düzenleme yapmayız: rabbit, animaldan kalıtım alır fakat onu değiştirmez. JavaScript motorları da bunu yüksek derecede optimize edebilir. Prototipi Object.setPrototypeOf veya obj.__proto__ ile sonradan değiştirmek oldukça yavaş bir operasyondur. Ama mümkündür.
“En basit” Objeler
Bildiğiniz gibi objeler anahtar/değer ikilisini tutan ilişkisel dizi şeklinde kullanılabilir.
…Eğer içerisinde kullanıcı-kaynaklı anahtarlar ( örneğin kullanıcının girdiği sözlük tipi veriler) var ise, ilginç bir aksaklık meydana gelir: "__proto_" haricinde tüm anahtarlar doğru çalışır.
Örneğin:
let obj = {};
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // [object Object] olarak döner , "some value" değil!
Eğer kullanıcı tipleri __proto__ içerisinde ise atama görmezden gelinir!
Bu aslında çok da sürpriz olmasa gerek. __proto__ farklı bir özelliktir: Ya obje olur veya null, mesela bir karakter dizisi prototip olamaz.
Fakat buradaki amacımız böyle bir davranışı uygulamak değildi, değil mi? Biz key/value ikililerini kaydetmekti. "__proto__" anahtarı düzgün bir şekilde kaydedilmedi. Bundan dolayı bu bir bugdır. Burada etkisi berbat değildir fakat diğer durumlarda prototip gerçekten değişebilir ve çalışma tamamen istenmeyen bir sonuca varabilir.
Daha kötüsü – genelde geliştiriciler böyle bir ihtimali düşünmezler bile. Bundan dolayı böyle bir bug’lar fark edilebilir ve saldırıya açık hale gelirler, özellikle JavaScript server tarafında kullanıldıysa.
Böyle bir olay sadece __proto__'da meydana gelir diğer tüm özellikler normalde “atanabilir”'dir.
Bu problemden nasıl kaçınılabilir?
Öncelikle Map kullanılabilir, her şey doğru çalışır.
Fakat burada bize Obje yardımcı olabilir, çünkü dili yaratıcılar bu konuları uzun zaman önce düşünmüşler.
__proto__ objenin bir özelliği değildir. Fakat Object.prototype’a erişim sağlar (accessor):
Bundan dolayı, Eğer obj.__proto__ okunur veya atanırsa, ilgili alıcı/ayarlayıcı prototipten çağırılır, böylece [[Prototoy]] alınır/ayarlanır.
Başlangıçta __proto__ , [[Prototype]]a erişmek için bir yol olarak tanımlanmıştır, [[Prototype]] değil.
Eğer, eğer objeyi ilişkisel dizi olarak kullanmak istiyorsanız şu şekilde yapabilirsiniz:
let obj = Object.create(null);
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // "some value"
Object.create(null) prototip’i olmayan boş bir obje yaratır.([[Prototype]] null’dur):
Bundan dolayı __proto__ için atadan kalan alıcı/ayarlayıcı bulunmamaktadır. Artık sıradan bir veri özelliği olarak işlenir, bundan dolayı yukarıdaki örnek doğru bir şekilde çalışır.
Böyle objelere “en basit” veya “saf sözlük objeleri” denir, Çünkü bunlar sıradan objelerden {...} bile daha basittirler.
Bu objelerin kötü tarafı ise, içinde hiçbir varsayılan metod bulunmaz, Örneğin: toString:
let obj = Object.create(null);
alert(obj); // Error (no toString)
…Fakat bu genelce ilişkili diziler için problem oluşturmaz.
Çoğu obje-bağlantılı metodlar Object.something(...) şeklindedir ve Object.keys(obj) gibi olduğundan prototipte yer almazlar, bundan dolayı şu şekilde çalışmaya devam ederler:
let chineseDictionary = Object.create(null);
chineseDictionary.hello = "ni hao";
chineseDictionary.bye = "zai jian";
alert(Object.keys(chineseDictionary)); // hello,bye
Tüm özellikleri alma
Bir objeden Anahtar/değer ikilisini almak için birçok yol bulunmaktadır.
Aşağıdaki kullanımını zaten biliyorsunuz:
- Object.keys(obj) / Object.values(obj) / Object.entries(obj) – kendi adı/değerleri/anahtar-değer ikilileri şeklinde döngü yapılabilir. Metodları sadece enumerable ( döngülenebilir ) ve anahtarları karakter dizisi olanlar .
Eğer sembolik özellikler istenirse:
- Object.getOwnPropertySymbols(obj) – kendi sembolik özellik isimlerini döndürür.
Eğer döngülenemez özellikleri istenirse:
- Object.getOwnPropertyNames(obj) – kendine ait tüm karakter dizisi özellikleri dizi olarak döner.
Eğer tüm özellikler istenir ise:
- Reflect.ownKeys(obj) – kendine ait tüm karakter dizisi özellikleri dizi olarak döner…
Bu metodlar hangi özellikleri döndürecekleri hususunda farklılıkları olsa da hepsi objenin kendi üzerinde çalışır. Prototipte bulunan özellikler listelenmez.
for..in döngüsü ise biraz farklıdır: Kalıtılmış özellikler’i de döngüye alır.
Örneğin:
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
// Sadece kendi anahtarları
alert(Object.keys(rabbit)); // jumps
// kalıtılmış anahtarları da içerir.
for(let prop in rabbit) alert(prop); // jumps, sonra eats
Eğer kalıtılmış özellikler ayrıştırılmak istenirse bunun için varolan obj.hasOwnProperty(key) kullanılabilir: Eğer obj key adında kalıtımsal olmayan bir özelliğe sahipse true dönderir.
Kalıtımsal özellikleri bu şekilde filtreleyebilir veya başka bir şey yapabiliriz:
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
alert(`${prop}: ${isOwn}`); // jumps:true, then eats:false
}
Şu şekilde kalıtım zinciri oluşmuştur: rabbit, sonra animal ve en son Object.prototype ( çünkü animal tam objedir {…} ), sonra bunun üzerine null:
Zincire bakarsanız rabbit.hasOwnProperty nereden geliyor görebilirsiniz. Object.prototype.hasOwnPropery, diğer bir deyişle kalıtılmış.
… Fakat öyleyse neden for..in içerisinde görünmüyor. Halbuki for..in ile kalıtılmış tüm özelliklerin listeleneceğini söylemiştik. Aslında cevap basit, bu döngüye alınamaz. Tıpkı diğer Object.prototype’larda olduğu gibi. Bundan dolayı listelenmemiştir.
Özet
Bu bölümde anlatılanların üzerinden kısaca geçecek olursak:
- Object.create(proto[, descriptors]) – verilen
protoile yeni bir obje yaratır, ayrıca opsiyonel olarak özellik tanımlıyıcılar verilebilir. - Object.getPrototypeOf(obj) –
obj’nin[[Prototype]]ını döner (__proto__alıcısı (getter) ile aynı işi yapar)). - Object.setPrototypeOf(obj, proto) –
obj’nin[[Prototype]]'ını verilenproto’ya ayarlar. (__proto__ayarlayıcısı (setter) ile aynı işi yapar) - Object.keys(obj) / Object.values(obj) / Object.entries(obj) – döngülenebilir karakter dizisi/ değerler/ anahtar-değer ikilisi dizisi döner.
- Object.getOwnPropertySymbols(obj) – tüm sembolik özelliklerin dizisini döner.
- Object.getOwnPropertyNames(obj) – özelliklerin tüm karakter dizisi isimlerini dizi olarak döner.
- Reflect.ownKeys(obj) – tüm özelliklerin isimlerini dizi olarak döner.
- obj.hasOwnProperty(key): Eğer
objkalıtılmış değilse vekeyadında bir özelliği varsatruedöner.
Şunu da belirtmiş olalım __proto__ [[Prototype]] için alıcı/ayarlayıcıdır. Bu Object.prototype içerisinde yer alır, tıpkı diğer metodlar gibi.
Prototip olmadan bir objeyi Object.create(null) şeklinde yaratmak mümkündür. Bu tür objeler “saf dictionary yapısı” olarak kullanılır. "__proto__" anahtarı ile bir problemi bulunmamaktadır.
Obje özelliklerini döndüren ( Object.keys ve diğerleri ) – “kendi” özelliklerini döndürür. Eğer kalıtılmış olanlarını da istersek for..in kullanabiliriz.
Yorumlar
<code>kullanınız, birkaç satır eklemek için ise<pre>kullanın. Eğer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)