जावास्क्रिप्ट में कक्षाएं और उदाहरणों को लागू करने के लिए दो मॉडल हैं: प्रोटोटाइप तरीका, और क्लोजर तरीका। दोनों के फायदे और कमियां हैं, और बहुत सारे विस्तारित विविधताएं हैं। कई प्रोग्रामर और पुस्तकालयों के पास भाषा के कुछ बदसूरत हिस्सों पर पेपर करने के लिए अलग-अलग दृष्टिकोण और क्लास-हैंडलिंग उपयोगिता फ़ंक्शंस हैं।
नतीजा यह है कि मिश्रित कंपनी में आपके पास मेटाक्लासेस का एक मिश्म होगा, सभी थोड़ा अलग तरीके से व्यवहार कर रहे हैं। क्या बुरा है, अधिकांश जावास्क्रिप्ट ट्यूटोरियल सामग्री भयानक है और सभी ठिकानों को कवर करने के लिए किसी तरह के बीच में समझौता करता है, जिससे आप बहुत भ्रमित हो जाते हैं। (संभवतः लेखक भी भ्रमित है। जावास्क्रिप्ट का ऑब्जेक्ट मॉडल अधिकांश प्रोग्रामिंग भाषाओं के लिए बहुत अलग है, और कई जगहों पर सीधे-सीधे बुरी तरह डिज़ाइन किया गया है।)
आइए प्रोटोटाइप तरीके से शुरू करें । यह सबसे अधिक जावास्क्रिप्ट-देशी है जिसे आप प्राप्त कर सकते हैं: न्यूनतम ओवरहेड कोड है और इस तरह के ऑब्जेक्ट के उदाहरणों के साथ इंस्टॉफ़ काम करेगा।
function Shape(x, y) {
this.x= x;
this.y= y;
}
हम new Shape
उन्हें इस prototype
रचनाकार के लुकअप में लिखकर बनाए गए इंस्टेंस के तरीकों को जोड़ सकते हैं :
Shape.prototype.toString= function() {
return 'Shape at '+this.x+', '+this.y;
};
अब इसे subclass करने के लिए, जितना आप कॉल कर सकते हैं उतना ही जावास्क्रिप्ट सबक्लासिंग करता है। हम उस अजीब जादू prototype
संपत्ति को पूरी तरह से बदलकर ऐसा करते हैं:
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();
इसमें विधियाँ जोड़ने से पहले:
Circle.prototype.toString= function() {
return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}
यह उदाहरण काम करेगा और आपको कई ट्यूटोरियल में कोड दिखाई देगा। लेकिन आदमी, वह new Shape()
बदसूरत है: हम आधार वर्ग को तात्कालिक कर रहे हैं, भले ही कोई वास्तविक आकार नहीं बनना है। यह इस सरल मामले में काम करने के लिए होता है क्योंकि जावास्क्रिप्ट इतना खराब है: यह अनुमति देता है शून्य तर्क, में पारित करने के लिए किया जा जिस स्थिति में x
और y
बन undefined
और प्रोटोटाइप के लिए आवंटित कर रहे हैं this.x
और this.y
। यदि कंस्ट्रक्टर फ़ंक्शन कुछ अधिक जटिल कर रहा था, तो यह उसके चेहरे पर फ्लैट हो जाएगा।
तो हमें जो करने की ज़रूरत है वह एक प्रोटोटाइप ऑब्जेक्ट बनाने का एक तरीका है जिसमें बेस क्लास के कंस्ट्रक्टर फ़ंक्शन को कॉल किए बिना तरीकों और अन्य सदस्यों को हम एक वर्ग स्तर पर चाहते हैं। ऐसा करने के लिए हमें हेल्पर कोड लिखना शुरू करना होगा। यह सबसे सरल तरीका है जो मुझे पता है:
function subclassOf(base) {
_subclassOf.prototype= base.prototype;
return new _subclassOf();
}
function _subclassOf() {};
यह बेस क्लास के सदस्यों को अपने प्रोटोटाइप में एक नए कंस्ट्रक्टर फंक्शन में ट्रांसफर करता है, जो कुछ नहीं करता है, फिर उस कंस्ट्रक्टर का इस्तेमाल करता है। अब हम बस लिख सकते हैं:
function Circle(x, y, r) {
Shape.call(this, x, y);
this.r= r;
}
Circle.prototype= subclassOf(Shape);
new Shape()
गलत के बजाय । अब हमारे पास निर्मित कक्षाओं के लिए प्राथमिकताओं का एक स्वीकार्य सेट है।
इस मॉडल के अंतर्गत हम कुछ परिशोधन और विस्तार पर विचार कर सकते हैं। उदाहरण के लिए यहां एक वाक्य रचना-चीनी संस्करण है:
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);
किसी भी संस्करण में दोष यह है कि कंस्ट्रक्टर फ़ंक्शन को विरासत में प्राप्त नहीं किया जा सकता है, क्योंकि यह कई भाषाओं में है। इसलिए, भले ही आपका उपवर्ग निर्माण प्रक्रिया में कुछ भी नहीं जोड़ता है, यह आधार निर्माता को उन तर्कों के साथ याद करना होगा जो आधार चाहते थे। इसका उपयोग करके इसे थोड़ा स्वचालित किया जा सकता है apply
, लेकिन फिर भी आपको लिखना होगा:
function Point() {
Shape.apply(this, arguments);
}
Point.subclass(Shape);
इसलिए एक सामान्य विस्तार यह है कि निर्माणकर्ता के बजाय अपने स्वयं के कार्य में आरंभीकरण सामग्री को बाहर निकालना। यह फ़ंक्शन तब आधार से ठीक वारिस हो सकता है:
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!
अब हमें प्रत्येक वर्ग के लिए एक ही निर्माता फ़ंक्शन बॉयलरप्लेट मिल गया है। हो सकता है कि हम इसे अपने स्वयं के सहायक कार्य में स्थानांतरित कर सकते हैं, इसलिए हमें इसे टाइप करने की आवश्यकता नहीं है, उदाहरण के लिए Function.prototype.subclass
, इसे गोल करने और बेस क्लास के फ़ंक्शन को उपवर्गों को बाहर करने दें:
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;
};
... जो अन्य भाषाओं की तरह थोड़ा अधिक दिखना शुरू हो रहा है, यद्यपि थोड़ी सी भी वाक्य रचना के साथ। आप चाहें तो कुछ अतिरिक्त सुविधाओं में भी छिड़काव कर सकते हैं। हो सकता है कि आप makeSubclass
एक कक्षा का नाम लेना और याद रखना चाहते हैं और toString
इसका उपयोग करके डिफ़ॉल्ट प्रदान करना चाहते हैं । हो सकता है कि आप कंस्ट्रक्टर को यह पता लगाना चाहते हैं कि इसे गलती से new
ऑपरेटर के बिना बुलाया गया है (जो अन्यथा अक्सर बहुत कष्टप्रद आलिंगन के परिणामस्वरूप होगा:
Function.prototype.makeSubclass= function() {
function Class() {
if (!(this instanceof Class))
throw('Constructor called without "new"');
...
हो सकता है कि आप सभी नए सदस्यों को पारित करना चाहते हैं और makeSubclass
उन्हें प्रोटोटाइप में जोड़ दिया है, Class.prototype...
जिससे आपको बहुत कुछ लिखना है। बहुत सारे क्लास सिस्टम ऐसा करते हैं, जैसे:
Circle= Shape.makeSubclass({
_init: function(x, y, z) {
Shape.prototype._init.call(this, x, y);
this.r= r;
},
...
});
कई संभावित विशेषताएं हैं जिन्हें आप ऑब्जेक्ट सिस्टम में वांछनीय मान सकते हैं और कोई भी वास्तव में एक विशेष सूत्र पर सहमत नहीं होता है।
बंद रास्ता है, तो। यह जावास्क्रिप्ट की प्रोटोटाइप-आधारित विरासत की समस्याओं से बचा जाता है, विरासत का उपयोग न करके। बजाय:
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();
अब हर एक उदाहरण के Shape
पास toString
विधि की अपनी प्रति होगी (और कोई अन्य विधि या अन्य वर्ग के सदस्य जो हम जोड़ते हैं)।
प्रत्येक वर्ग के प्रत्येक सदस्य की अपनी प्रति होने की बुरी बात यह है कि यह कम कुशल है। यदि आप बड़ी संख्या में उप-वर्ग उदाहरणों के साथ काम कर रहे हैं, तो प्रोटोटाइपीय विरासत आपको बेहतर सेवा दे सकती है। बेस क्लास की एक पद्धति को कॉल करना थोड़ा कष्टप्रद है जैसा कि आप देख सकते हैं: हमें यह याद रखना होगा कि उपवर्ग निर्माता ने इसे ओवरवोट करने से पहले क्या तरीका अपनाया था, या यह खो गया।
[भी क्योंकि यहाँ कोई विरासत नहीं है, instanceof
ऑपरेटर काम नहीं करेगा; यदि आपको इसकी आवश्यकता है तो आपको कक्षा-सूँघने के लिए अपना तंत्र प्रदान करना होगा। जब तक आप प्रोटोटाइप इनहेरिटेंस के साथ प्रोटोटाइप ऑब्जेक्ट्स को एक समान तरीके से फील कर सकते हैं , यह थोड़ा मुश्किल है और वास्तव में instanceof
काम करने के लिए इसके लायक नहीं है ।]
हर उदाहरण की अपनी विधि होने के बारे में अच्छी बात यह है कि यह विधि उस विशिष्ट उदाहरण के लिए बाध्य हो सकती है जो इसका मालिक है। यह जावास्क्रिप्ट के अजीब तरीके से बाइंडिंग this
मेथड कॉल करने के कारण उपयोगी है , जिसमें अपशॉट है कि यदि आप किसी विधि को उसके मालिक से अलग करते हैं:
var ts= mycircle.toString;
alert(ts());
तब this
विधि के अंदर अपेक्षा के अनुसार सर्किल उदाहरण नहीं होगा (यह वास्तव में वैश्विक window
ऑब्जेक्ट होगा, जिससे व्यापक डिबगिंग शोक होगा)। वास्तव में यह आमतौर पर तब होता है जब एक विधि को लिया जाता है और उसे सौंपा जाता है setTimeout
, onclick
या EventListener
सामान्य रूप से।
प्रोटोटाइप तरीके के साथ, आपको हर ऐसे असाइनमेंट के लिए एक क्लोजर शामिल करना होगा:
setTimeout(function() {
mycircle.move(1, 1);
}, 1000);
या, भविष्य में (या अब यदि आप Function.prototype को हैक करते हैं) तो आप इसके साथ भी कर सकते हैं function.bind()
:
setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);
यदि आपके उदाहरणों को बंद करने के तरीके से किया जाता है, तो बंधन को उदाहरण चर (आमतौर पर कहा जाता है that
या self
हालांकि, व्यक्तिगत रूप से मैं बाद में self
पहले से ही जावास्क्रिप्ट में अलग अर्थ है) के खिलाफ सलाह देगा, द्वारा बंद करने के लिए मुफ्त में किया जाता है । 1, 1
हालांकि, उपरोक्त स्निपेट में आपको तर्क-वितर्क मुफ्त में नहीं मिलते हैं , इसलिए आपको अभी भी एक और बंद करने की आवश्यकता होगी या bind()
यदि आपको ऐसा करने की आवश्यकता है।
क्लोजर विधि पर भी बहुत सारे वेरिएंट हैं। आप this
पूरी तरह से चूकना पसंद कर सकते हैं , एक नया बनाने that
और 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
कौन सा तरीका "उचित" है? दोनों। कौन सा "सर्वश्रेष्ठ" है? जो आपकी स्थिति पर निर्भर करता है। FWIW मैं वास्तविक जावास्क्रिप्ट वंशानुक्रम के लिए प्रोटोटाइप की ओर जाता हूं जब मैं दृढ़ता से OO सामान कर रहा हूं, और सरल फेंकने वाले पृष्ठ प्रभावों के लिए बंद हो जाता हूं।
लेकिन दोनों तरीके अधिकांश प्रोग्रामर के लिए काफी सहज हैं। दोनों में कई संभावित गड़बड़ बदलाव हैं। यदि आप अन्य लोगों के कोड / पुस्तकालयों का उपयोग करते हैं, तो आप दोनों (साथ ही बीच में और आम तौर पर टूटी हुई योजनाओं) से मिलेंगे। आम तौर पर स्वीकृत उत्तर कोई नहीं है। जावास्क्रिप्ट वस्तुओं की अद्भुत दुनिया में आपका स्वागत है।
[यह जावास्क्रिप्ट क्यों मेरी पसंदीदा प्रोग्रामिंग भाषा नहीं है का हिस्सा 94 है।]