Bu cevabın 3 yıl geciktiğini biliyorum, ancak mevcut cevapların prototip mirasının klasik mirastan daha iyi olduğu hakkında yeterli bilgi sağlamadığını düşünüyorum .
İlk olarak JavaScript programcılarının prototip mirasını savunmak için belirttikleri en yaygın argümanları görelim (bu argümanları mevcut cevap havuzundan alıyorum):
- Basit.
- Güçlü.
- Daha küçük, daha az yedekli kod sağlar.
- Dinamik ve dolayısıyla dinamik diller için daha iyi.
Şimdi bu argümanların hepsi geçerli, ama kimse nedenini açıklamakla uğraşmadı. Bir çocuğa Matematik öğrenmenin önemli olduğunu söylemek gibi. Elbette öyle, ama çocuk kesinlikle umursamıyor; ve bunun önemli olduğunu söyleyerek Maths gibi bir çocuğu yapamazsınız.
Prototip palet miras sorun JavaScript açısından açıklanması olduğunu düşünüyorum. JavaScript'i seviyorum, ancak JavaScript'teki prototip kalıtım yanlış. Klasik kalıtımdan farklı olarak iki prototip kalıtım paterni vardır:
- Prototip paletinin prototip modeli.
- Prototip paletinin yapıcı modeli.
Ne yazık ki JavaScript prototypal mirasının yapıcı modelini kullanıyor. Çünkü JavaScript oluşturulduğunda, Brendan Eich (JS'nin yaratıcısı) Java'ya (klasik mirasa sahip) benzemesini istedi:
Ve Java'yı küçük bir kardeş olarak itiyorduk, o sırada Visual Basic gibi tamamlayıcı bir dil Microsoft'un dil ailelerinde C ++ içindi.
Bu kötü çünkü insanlar JavaScript'te kurucu kullandıklarında diğer kuruculardan miras kalan kurucuları düşünüyorlar. Bu yanlış. Prototip paletinde miras nesneleri diğer nesnelerden miras alır. Yapıcılar asla resme girmezler. Çoğu insanın kafasını karıştıran şey budur.
Klasik kalıtıma sahip Java gibi dillerden insanlar daha da karışıyor çünkü kurucular sınıflara benzese de sınıf gibi davranmıyorlar. As Douglas Crockford belirtti:
Bu dolaylı anlatım, dilin klasik olarak eğitilmiş programcılara daha tanıdık gelmesini sağlamaya yönelikti, ancak Java programcılarının JavaScript'e sahip olduğu çok düşük görüşten görebildiğimiz gibi bunu başaramadı. JavaScript'in yapıcı modeli klasik kalabalığa hitap etmedi. Ayrıca JavaScript'in gerçek prototip yapısını da gizledi. Sonuç olarak, dili etkili bir şekilde kullanmayı bilen çok az programcı vardır.
İşte aldın. Atın ağzından.
Gerçek Prototip Paleti
Prototip palet miras tamamen nesnelerle ilgilidir. Nesneler özellikleri diğer nesnelerden devralır. Hepsi bu kadar. Prototip paletini kullanarak nesne oluşturmanın iki yolu vardır:
- Yepyeni bir nesne oluşturun.
- Mevcut bir nesneyi klonlayın ve genişletin.
Not: JavaScript, bir nesneyi klonlamak için iki yol sunar - temsilci seçme ve birleştirme . Bundan sonra, yalnızca delegasyon yoluyla mirasa atıfta bulunmak için "klon" kelimesini ve sadece birleştirme yoluyla mirasa atıfta bulunmak için "kopya" kelimesini kullanacağım.
Bu kadar konuşma yeter. Hadi bazı örneklere bakalım. Diyelim ki bir yarıçap çemberi var 5
:
var circle = {
radius: 5
};
Dairenin alanını ve çevresini yarıçapından hesaplayabiliriz:
circle.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
circle.circumference = function () {
return 2 * Math.PI * this.radius;
};
Şimdi başka bir yarıçap dairesi oluşturmak istiyorum 10
. Bunu yapmanın bir yolu:
var circle2 = {
radius: 10,
area: circle.area,
circumference: circle.circumference
};
Ancak JavaScript daha iyi bir yol sağlar - temsilci seçme . Object.create
Fonksiyon Bunu yapmak için kullanılır:
var circle2 = Object.create(circle);
circle2.radius = 10;
Bu kadar. JavaScript'te prototip palet miras yaptınız. Basit değil miydi? Bir nesneyi alırsınız, klonlar, ihtiyacınız olanı değiştirirsiniz ve hey presto - kendinize yepyeni bir nesne elde edersiniz.
Şimdi, "Bu nasıl basit? Her yeni bir çevre oluşturmak istediğimde, klonlayıp circle
elle bir yarıçap atamam gerekiyor" diye sorabilirsiniz . Çözüm, ağır kaldırma işlemini sizin için yapmak için bir işlev kullanmaktır:
function createCircle(radius) {
var newCircle = Object.create(circle);
newCircle.radius = radius;
return newCircle;
}
var circle2 = createCircle(10);
Aslında tüm bunları aşağıdaki gibi tek bir nesne değişmezinde birleştirebilirsiniz:
var circle = {
radius: 5,
create: function (radius) {
var circle = Object.create(this);
circle.radius = radius;
return circle;
},
area: function () {
var radius = this.radius;
return Math.PI * radius * radius;
},
circumference: function () {
return 2 * Math.PI * this.radius;
}
};
var circle2 = circle.create(10);
JavaScript'te Prototypal Kalıtım
Yukarıdaki programda, create
fonksiyonun bir klonu oluşturduğunu fark ederseniz circle
, radius
ona bir yeni atar ve ardından döndürür. Bir kurucu JavaScript'te tam olarak bunu yapar:
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
Circle.prototype.circumference = function () {
return 2 * Math.PI * this.radius;
};
var circle = new Circle(5);
var circle2 = new Circle(10);
JavaScript'teki yapıcı modeli, ters çevrilmiş prototip kalıptır. Bir nesne oluşturmak yerine bir kurucu oluşturursunuz. new
Kelime bağlanan this
bir klonuna kurucu içinde işaretçi prototype
yapıcı.
Kafa karıştırıcı geliyor mu? Çünkü JavaScript'teki yapıcı deseni gereksiz yere işleri karmaşık hale getirir. Çoğu programcının anlaması zor olan budur.
Diğer nesnelerden miras kalan nesneleri düşünmek yerine, diğer kuruculardan miras kalan yapıcıları düşünürler ve sonra tamamen karışırlar.
JavaScript'teki yapıcı modelinden kaçınılması gereken başka birçok neden daha var. Bunları blog yayınımda okuyabilirsiniz: Constructors vs Prototypes
Peki prototip mirasın klasik mirastan faydaları nelerdir? En yaygın argümanları tekrar gözden geçirelim ve nedenini açıklayalım .
1. Prototypal Kalıtım Basit
CMS cevabında şöyle diyor:
Bence prototip kalıtımın en büyük yararı basitliğidir.
Az önce ne yaptığımızı düşünelim. circle
Yarıçapı olan bir nesne yarattık 5
. Sonra klonladık ve klonun yarıçapını verdik 10
.
Bu nedenle prototip mirasın işe yaraması için sadece iki şeye ihtiyacımız var:
- Yeni bir nesne oluşturmanın bir yolu (örneğin, nesne değişmez değerleri).
- Mevcut bir nesneyi genişletmenin bir yolu (örn.
Object.create
).
Buna karşılık klasik kalıtım çok daha karmaşıktır. Klasik mirasta:
- Sınıflar.
- Nesne.
- Arabirimler.
- Soyut dersler.
- Final Sınıfları.
- Sanal Temel Sınıflar.
- Yapıcılar.
- Yıkıcılar.
Kaptın bu işi. Mesele şu ki prototip mirasın anlaşılması daha kolay, uygulanması daha kolay ve akıl yürütmesi daha kolay.
Steve Yegge'nin klasik blog yazısı " N00b'nin Portresi " ne koyduğu gibi :
Meta veriler, başka bir şeyin herhangi bir açıklaması veya modelidir. Kodunuzdaki yorumlar yalnızca hesaplamanın doğal bir açıklamasıdır. Meta veri meta verilerini yapan şey, kesinlikle gerekli olmamasıdır. Bazı soyağacı evrakları olan bir köpeğim varsa ve evrakları kaybedersem, hala geçerli bir köpeğim var.
Aynı anlamda sınıflar sadece meta verilerdir. Kalıtım için sınıflara kesinlikle gerek yoktur. Ancak bazı insanlar (genellikle n00bs) dersleri çalışmak için daha rahat bulurlar. Onlara yanlış bir güvenlik duygusu verir.
Ayrıca, statik türlerin sadece meta veri olduğunu da biliyoruz. İki tür okuyucuya yönelik özel bir yorumdur: programcılar ve derleyiciler. Statik türler, muhtemelen her iki okuyucu grubunun da programın amacını anlamasına yardımcı olmak için hesaplama hakkında bir hikaye anlatır. Ancak statik türler çalışma zamanında atılabilir, çünkü sonunda sadece stilize yorumlar. Soy ağacı evrakları gibidirler: köpekleri hakkında belirli bir güvensiz kişilik tipini daha mutlu edebilir, ancak köpek kesinlikle umursamaz.
Daha önce de belirttiğim gibi, sınıflar insanlara sahte bir güvenlik duygusu verir. Örneğin NullPointerException
, kodunuz mükemmel okunabilir olsa bile Java'da çok fazla s alırsınız . Klasik kalıtımın genellikle programlama yoluna girdiğini düşünüyorum, ama belki de sadece Java. Python inanılmaz bir klasik kalıtım sistemine sahiptir.
2. Prototypal Kalıtım Güçlüdür
Klasik bir arka plandan gelen çoğu programcı, klasik kalıtımın prototip kalıtımdan daha güçlü olduğunu savunur çünkü:
- Özel değişkenler.
- Çoklu kalıtım.
Bu iddia yanlıştır. JavaScript'in özel değişkenleri kapanışlarla desteklediğini zaten biliyoruz , ancak çoklu kalıtım ne olacak? JavaScript'teki nesnelerin yalnızca bir prototipi vardır.
Gerçek şu ki prototip kalıtım birden fazla prototipten miras almayı destekler. Prototip palet devri, bir nesnenin başka bir nesneden miras alınması anlamına gelir. Aslında prototip mirasını uygulamanın iki yolu vardır :
- Yetki Devri veya Diferansiyel Miras
- Klonlama veya Birleştirici Kalıtım
Evet JavaScript yalnızca nesnelerin başka bir nesneye yetki vermesine izin verir. Ancak, rastgele sayıda nesnenin özelliklerini kopyalamanızı sağlar. Örneğin _.extend
, sadece bunu yapar.
Elbette birçok programcı bu doğrudur miras olarak düşünmüyoruz nedeniyle instanceof
ve isPrototypeOf
başka türlü söylemek. Ancak bu, bir prototipten birleştirme yoluyla devralınan her nesne üzerinde bir dizi prototip saklanarak kolayca giderilebilir:
function copyOf(object, prototype) {
var prototypes = object.prototypes;
var prototypeOf = Object.isPrototypeOf;
return prototypes.indexOf(prototype) >= 0 ||
prototypes.some(prototypeOf, prototype);
}
Bu nedenle prototip kalıtım, klasik kalıtım kadar güçlüdür. Aslında, klasik kalıtımdan çok daha güçlüdür, çünkü prototip mirasında, farklı prototiplerden hangi özellikleri kopyalayacağınızı ve hangi özellikleri atlayacağınızı seçebilirsiniz.
Klasik kalıtımda, miras almak istediğiniz özellikleri seçmek imkansızdır (veya en azından çok zordur). Elmas problemini çözmek için sanal temel sınıfları ve arayüzleri kullanıyorlar .
Ancak JavaScript'te büyük olasılıkla elmas sorununu asla duymayacaksınız, çünkü hangi özellikleri devralmak istediğinizi ve hangi prototiplerden kontrol edebileceğinizi tam olarak kontrol edebilirsiniz.
3. Prototip Paleti Daha Az Gereksizdir
Bu noktayı açıklamak biraz daha zordur çünkü klasik kalıtım zorunlu olarak daha fazla gereksiz koda yol açmaz. Aslında miras, ister klasik ister prototip, koddaki fazlalığı azaltmak için kullanılır.
Bir argüman, klasik kalıtımı olan çoğu programlama dilinin statik olarak yazılması ve kullanıcının türleri açıkça belirtmesini gerektirmesidir (örtülü statik yazmaya sahip Haskell'den farklı olarak). Dolayısıyla bu daha ayrıntılı koda yol açar.
Java bu davranış için kötü şöhretlidir. Bob Nystrom'un blog yazısında Pratt Parsers hakkındaki aşağıdaki fıkradan bahsettiğini açıkça hatırlıyorum :
Burada Java'nın "lütfen dört katına imzalayın" bürokrasi seviyesini sevmelisiniz.
Yine, bunun sadece Java'nın çok fazla emmesi nedeniyle olduğunu düşünüyorum.
Geçerli bir argüman klasik mirası olan tüm dillerin çoklu mirası desteklemediğidir. Yine Java akla geliyor. Evet Java'nın arayüzleri var, ama bu yeterli değil. Bazen gerçekten birden fazla mirasa ihtiyacınız vardır.
Prototipal kalıtım çoklu kalıtıma izin verdiğinden, çoklu kalıtım gerektiren kod, klasik kalıtım olan ancak çoklu kalıtım olmayan bir dilde değil, prototip kalıtım kullanılarak yazıldığında daha az gereksizdir.
4. Prototip Paleti Dinamiktir
Prototip paletinin en önemli avantajlarından biri, prototiplere oluşturulduktan sonra yeni özellikler ekleyebilmenizdir. Bu, bir prototipe, o prototip için temsilci olan tüm nesneler için otomatik olarak kullanılabilir hale getirilecek yeni yöntemler eklemenizi sağlar.
Klasik mirasta bu mümkün değildir, çünkü bir sınıf oluşturulduktan sonra onu çalışma zamanında değiştiremezsiniz. Bu muhtemelen prototipal kalıtımın klasik kalıtımdan en büyük avantajı ve en üstte olması gerekiyordu. Ancak ben sonuna kadar en iyi tasarruf seviyorum.
Sonuç
Prototip palet mirası önemlidir. JavaScript programcılarını prototip mirasının yapıcı modelini neden prototip mirasının prototip deseni lehine terk edecekleri konusunda eğitmek önemlidir.
JavaScript'i doğru bir şekilde öğretmeye başlamamız gerekiyor ve bu da yeni programcılara yapıcı modeli yerine prototip desenini kullanarak nasıl kod yazacaklarını göstermek anlamına geliyor.
Prototip paletini kullanarak prototip kalıtımını açıklamak daha kolay olmakla kalmaz, aynı zamanda daha iyi programcılar yapar.
Bu yanıtı beğendiyseniz , " Neden Prototypal Miras Önemlidir " konulu blog yayınımı da okumalısınız . Güven bana, hayal kırıklığına uğramayacaksınız.