JavaScript'te sınıfları ve örnekleri uygulamak için iki model vardır: prototip oluşturma yolu ve kapatma yolu. Her ikisinin de avantajları ve dezavantajları vardır ve birçok genişletilmiş varyasyon vardır. Birçok programcı ve kütüphane, dilin bazı çirkin kısımlarını ele almak için farklı yaklaşımlara ve sınıf yönetimi yardımcı programlarına sahiptir.
Sonuç, karışık şirkette, biraz farklı davranan bir metasınıf karması olacak. Daha da kötüsü, çoğu JavaScript öğretici materyali korkunçtur ve tüm üsleri kapsayacak şekilde bir çeşit uzlaşma sağlar ve sizi çok karışık bırakır. (Muhtemelen yazarın kafası da karışmıştır. JavaScript'in nesne modeli çoğu programlama dilinden çok farklıdır ve birçok yerde düzensiz tasarlanmıştır.)
Prototip yolu ile başlayalım . Bu alabileceğiniz en JavaScript yereldir: minimum ek yük kodu vardır ve instanceof bu tür nesnelerin örnekleriyle çalışacaktır.
function Shape(x, y) {
this.x= x;
this.y= y;
}
Oluşturulan örneğe , bu yapıcı işlevinin aranmasına new Shape
yazarak yöntemler ekleyebiliriz prototype
:
Shape.prototype.toString= function() {
return 'Shape at '+this.x+', '+this.y;
};
Şimdi alt sınıflamak için, JavaScript'in alt sınıflandırma yaptıklarını çağırabildiğiniz kadar. Bunu, tuhaf büyü prototype
özelliğini tamamen değiştirerek yapıyoruz :
function Circle(x, y, r) {
Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
this.r= r;
}
Circle.prototype= new Shape();
ona yöntem eklemeden önce:
Circle.prototype.toString= function() {
return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}
Bu örnek işe yarayacak ve birçok öğreticide bunun gibi bir kod göreceksiniz. Ama adamım, bu new Shape()
çirkin: hiçbir gerçek Şekil yaratılmamasına rağmen temel sınıfı somutlaştırıyoruz. JavaScript kadar saçma olduğu için bu basit durumda işin olur: sıfır argümanlar hangi durumda, normal olarak iletilmesine izin veren x
ve y
olmak undefined
ve prototip en atanır this.x
ve this.y
. Yapıcı işlevi daha karmaşık bir şey yapıyor olsaydı, yüzüne düz düşecekti.
Yapmamız gereken şey, temel sınıfın yapıcı işlevini çağırmadan, sınıf düzeyinde istediğimiz yöntemleri ve diğer üyeleri içeren bir prototip nesnesi oluşturmak için bir yol bulmaktır. Bunu yapmak için yardımcı kod yazmaya başlamamız gerekecek. Bu bildiğim en basit yaklaşım:
function subclassOf(base) {
_subclassOf.prototype= base.prototype;
return new _subclassOf();
}
function _subclassOf() {};
Bu, temel sınıfın prototipindeki üyelerini, hiçbir şey yapmayan yeni bir yapıcı işlevine aktarır, daha sonra bu yapıcıyı kullanır. Şimdi basitçe yazabiliriz:
function Circle(x, y, r) {
Shape.call(this, x, y);
this.r= r;
}
Circle.prototype= subclassOf(Shape);
new Shape()
yanlışlık yerine . Artık inşa edilmiş sınıflar için kabul edilebilir bir ilkel setimiz var.
Bu model altında ele alabileceğimiz birkaç ayrıntılandırma ve uzantı var. Örneğin, sözdizimsel şeker versiyonu:
Function.prototype.subclass= function(base) {
var c= Function.prototype.subclass.nonconstructor;
c.prototype= base.prototype;
this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};
...
function Circle(x, y, r) {
Shape.call(this, x, y);
this.r= r;
}
Circle.subclass(Shape);
Her iki sürümde de, birçok dilde olduğu gibi yapıcı işlevinin devralınamaması dezavantajı vardır. Bu nedenle, alt sınıfınız inşaat sürecine hiçbir şey eklemese bile, taban yapıcısına, tabanın istediği bağımsız değişkenleri çağırmayı hatırlamalıdır. Bu, kullanarak biraz otomatik hale getirilebilir apply
, ancak yine de yazmanız gerekir:
function Point() {
Shape.apply(this, arguments);
}
Point.subclass(Shape);
Bu nedenle yaygın bir uzantı, başlatma öğelerini yapıcıdan ziyade kendi işlevine ayırmaktır. Bu işlev daha sonra tabandan iyi bir şekilde miras alabilir:
function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
this.x= x;
this.y= y;
};
function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!
Şimdi her sınıf için aynı yapıcı işlev kazanı var. Belki bunu kendi yardımcı işlevine taşıyabiliriz, örneğin yazmaya devam etmek zorunda kalmamıza gerek kalmaz, onu Function.prototype.subclass
çevirmek ve temel sınıfın İşlevinin alt sınıfları tükürmesine izin vermek yerine :
Function.prototype.makeSubclass= function() {
function Class() {
if ('_init' in this)
this._init.apply(this, arguments);
}
Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};
...
Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
this.x= x;
this.y= y;
};
Point= Shape.makeSubclass();
Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
Shape.prototype._init.call(this, x, y);
this.r= r;
};
... biraz daha dağınık sözdizimiyle de olsa, diğer dillere benzemeye başlıyor. İsterseniz birkaç ekstra özellik serpebilirsiniz. Belki de makeSubclass
bir sınıf adını alıp hatırlamak ve toString
bunu kullanarak varsayılan bir ad vermek istersiniz . Belki de yapıcıyı new
operatör olmadan yanlışlıkla çağrıldığında algılamak istersiniz (aksi takdirde genellikle çok can sıkıcı hata ayıklama ile sonuçlanır):
Function.prototype.makeSubclass= function() {
function Class() {
if (!(this instanceof Class))
throw('Constructor called without "new"');
...
Belki de tüm yeni üyeleri geçmek ve makeSubclass
onları Class.prototype...
çok fazla yazmak zorunda kalmamak için prototipe eklemek istiyorsunuz . Birçok sınıf sistemi bunu yapar, örneğin:
Circle= Shape.makeSubclass({
_init: function(x, y, z) {
Shape.prototype._init.call(this, x, y);
this.r= r;
},
...
});
Bir nesne sisteminde istenebileceğini düşündüğünüz birçok potansiyel özellik vardır ve hiç kimse belirli bir formülü gerçekten kabul etmez.
Kapatma yolu , o zaman. Bu, JavaScript'i prototip tabanlı kalıtım sorunlarından kaçınarak kalıtım kullanmaz. Yerine:
function Shape(x, y) {
var that= this;
this.x= x;
this.y= y;
this.toString= function() {
return 'Shape at '+that.x+', '+that.y;
};
}
function Circle(x, y, r) {
var that= this;
Shape.call(this, x, y);
this.r= r;
var _baseToString= this.toString;
this.toString= function() {
return 'Circular '+_baseToString(that)+' with radius '+that.r;
};
};
var mycircle= new Circle();
Şimdi her bir örneğinin Shape
kendi toString
yöntem kopyası (ve eklediğimiz diğer yöntemler veya diğer sınıf üyeleri) olacaktır.
Her sınıf üyesinin kendi kopyasına sahip her örnek için kötü olan şey, daha az verimli olmasıdır. Çok sayıda alt sınıf örneği ile uğraşıyorsanız, prototipsel miras size daha iyi hizmet edebilir. Ayrıca, temel sınıfın bir yöntemini çağırmak, gördüğünüz gibi biraz can sıkıcıdır: alt sınıf yapıcısının üzerine yazmadan önce yöntemin ne olduğunu hatırlamamız gerekir veya kaybolur.
[Ayrıca burada kalıtım olmadığından, instanceof
operatör çalışmaz; İhtiyacınız olursa sınıf koklama için kendi mekanizmanızı sağlamalısınız. Eğer iken olabilir prototip miras olduğu gibi benzer bir şekilde prototip nesneleri keman, bunun sadece almak için gerçekten değer biraz zor ve değil instanceof
çalışma.]
Kendi yöntemine sahip her örnek için iyi olan şey, yöntemin daha sonra sahip olduğu belirli örneğe bağlanabilmesidir. Bu, JavaScript'in this
yöntem çağrılarındaki garip bağlanma yöntemi nedeniyle kullanışlıdır , ki bu yöntem, bir yöntemi sahibinden ayırırsanız:
var ts= mycircle.toString;
alert(ts());
Daha sonra this
beklendiği gibi yöntem Çember örneği olmayacak içindeki (aslında küresel olacağım window
yaygın hata ayıklama vah neden nesne). Bir yöntem alınır ve bir atandığında Gerçekte bu durumla karşılaşılır setTimeout
, onclick
ya EventListener
genel olarak.
Prototip yöntemiyle, bu tür her atama için bir kapatma eklemeniz gerekir:
setTimeout(function() {
mycircle.move(1, 1);
}, 1000);
ya da gelecekte (ya da şimdi Function.prototype'i hack ederseniz) aşağıdakileri de yapabilirsiniz function.bind()
:
setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);
örnekleriniz kapanış yolunda yapıldıysa, bağlayıcı, örnek değişkeni üzerindeki kapak tarafından ücretsiz olarak yapılır (genellikle çağrılır that
veya self
kişisel olarak self
, JavaScript'e başka bir anlamı olduğu için ikincisine karşı öneririm ). 1, 1
Yukarıdaki snippet'teki argümanları ücretsiz olarak almazsınız , bu yüzden yine de başka bir kapanışa veya bind()
bunu yapmanız gerekiyorsa.
Kapatma yönteminde de birçok varyant var. Operatörü kullanmak yerine this
tamamen atlamayı , yeni bir tane oluşturmayı that
ve geri göndermeyi tercih edebilirsiniz new
:
function Shape(x, y) {
var that= {};
that.x= x;
that.y= y;
that.toString= function() {
return 'Shape at '+that.x+', '+that.y;
};
return that;
}
function Circle(x, y, r) {
var that= Shape(x, y);
that.r= r;
var _baseToString= that.toString;
that.toString= function() {
return 'Circular '+_baseToString(that)+' with radius '+r;
};
return that;
};
var mycircle= Circle(); // you can include `new` if you want but it won't do anything
Hangi yol “uygun”? Her ikisi de. Hangisi en iyi"? Bu sizin durumunuza bağlıdır. FWIW Ben güçlü OO şeyler yaparken basit JavaScript kalıtım için prototipleme eğilimi ve basit tek kullanımlık sayfa efektleri için kapanış eğilimi.
Ancak her iki yol da çoğu programcı için oldukça sezgiseldir. Her ikisinin de birçok potansiyel dağınık varyasyonu vardır. Başkalarının kodlarını / kitaplıklarını kullanırsanız, her ikisiyle de karşılaşacaksınız (aralarında ve genellikle bozuk olan birçok programda). Genel kabul görmüş bir cevap yok. JavaScript nesnelerinin harika dünyasına hoş geldiniz.
[Bu neden JavaScript'in En Sevdiğim Programlama Dili Değil?