JavaScript में ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग (OOP) की क्षमताएँ मौजूद हैं। OOP में आप "टेम्पलेट" (क्लास) से ऑब्जेक्ट (इंस्टेंस) बनाते हैं, ताकि उनमें कुछ खास डेटा और कार्यक्षमता शामिल हो। OOP के संदर्भ में डेटा वाली प्रॉपर्टी को फील्ड कहते हैं, और फंक्शन वाली प्रॉपर्टी को मेथड।
2015 में भाषा के स्पेसिफिकेशन में क्लास जोड़े जाने से पहले JavaScript में क्लास जैसी कोई चीज़ नहीं थी।
लेकिन प्रोटोटाइप-आधारित इनहेरिटेंस की मदद से ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग करना फिर भी संभव था।
और आज class कीवर्ड उपलब्ध होने के बावजूद JavaScript अब भी एक प्रोटोटाइप-आधारित भाषा है।
प्रोटोटाइप-आधारित भाषा का मतलब क्या होता है और JavaScript असल में कैसे काम करती है, यह समझने के लिए चलिए उस समय पर लौटते हैं जब क्लास जैसी कोई चीज़ नहीं थी।
JavaScript में टेम्पलेट (क्लास) का काम एक आम फंक्शन करता है।
जब किसी फंक्शन को इस तरह के टेम्पलेट के रूप में इस्तेमाल करना होता है, तो उसे कंस्ट्रक्टर फंक्शन कहते हैं।
इसका एक आम नियम यह है कि फंक्शन के नाम का पहला अक्षर बड़ा रखा जाता है।
कंस्ट्रक्टर फंक्शन को कॉल करते समय new कीवर्ड की मदद से टेम्पलेट से इंस्टेंस (ऑब्जेक्ट) बनाए जाते हैं।
function Car() {
// ...
}
const myCar = new Car();
const yourCar = new Car();
ध्यान देना ज़रूरी है कि JavaScript में इंस्टेंस बन जाने के बाद भी इंस्टेंस और कंस्ट्रक्टर फंक्शन का आपसी रिश्ता बना रहता है।
हर इंस्टेंस ऑब्जेक्ट के अंदर एक छिपी हुई आंतरिक प्रॉपर्टी होती है, जिसे भाषा के स्पेसिफिकेशन में [[prototype]] कहा जाता है।
इसमें कंस्ट्रक्टर फंक्शन की prototype प्रॉपर्टी की वैल्यू का रेफरेंस रखा जाता है।
जी हाँ, आपने ठीक पढ़ा, JavaScript के फंक्शन में की-वैल्यू जोड़े हो सकते हैं, क्योंकि पर्दे के पीछे वह भी एक ऑब्जेक्ट है।
2015 से [[prototype]] तक Object.getPrototypeOf() के ज़रिए पहुँचा जा सकता है।
उससे पहले कई वातावरणों में इस तक पहुँचने के लिए __proto__ की इस्तेमाल होता था।
किसी ऑब्जेक्ट के प्रोटोटाइप ([[prototype]]) और कंस्ट्रक्टर फंक्शन की prototype प्रॉपर्टी को आपस में मत मिलाइए।
संक्षेप में:
prototype प्रॉपर्टी ही इंस्टेंस का प्रोटोटाइप बन जाती है।अक्सर आप चाहते हैं कि सभी बनने वाले ऑब्जेक्ट (इंस्टेंस) में कुछ फील्ड हों, और ऑब्जेक्ट बनाते समय उनके लिए कुछ शुरुआती वैल्यू भी दी जाएँ।
यह this कीवर्ड की मदद से किया जा सकता है।
कंस्ट्रक्टर फंक्शन के अंदर this उस नए ऑब्जेक्ट को दर्शाता है जो new के ज़रिए बनेगा।
कंस्ट्रक्टर फंक्शन को new के साथ कॉल करने पर this अपने आप लौटा दिया जाता है।
इसका मतलब है कि कंस्ट्रक्टर फंक्शन में this के साथ फील्ड जोड़कर हम नए इंस्टेंस में फील्ड जोड़ सकते हैं।
function Car(color, weight) {
this.color = color;
this.weight = weight;
this.engineRunning = false;
}
const myCar = new Car('red', '2mt');
myCar.color;
// => 'red'
myCar.engineRunning;
// => false
मेथड कंस्ट्रक्टर फंक्शन की prototype प्रॉपर्टी के ज़रिए जोड़े जाते हैं।
किसी मेथड के अंदर आप this के ज़रिए इंस्टेंस की फील्ड तक पहुँच सकते हैं।
यह इस आम नियम की वजह से काम करता है।
जब किसी फंक्शन को किसी ऑब्जेक्ट के मेथड के रूप में कॉल किया जाता है, तो उसका
thisउसी ऑब्जेक्ट पर सेट हो जाता है जिस पर वह मेथड कॉल किया गया। 1
function Car() {
this.engineRunning = false;
// ...
}
Car.prototype.startEngine = function () {
this.engineRunning = true;
};
Car.prototype.addGas = function (litre) {
// ...
};
const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true
ऊपर के उदाहरण में myCar एक आम JavaScript ऑब्जेक्ट है। अगर हम इसे जाँचें (जैसे ब्राउज़र कंसोल में), तो myCar ऑब्जेक्ट के अंदर सीधे startEngine नाम की कोई प्रॉपर्टी नहीं मिलेगी जिसकी वैल्यू कोई फंक्शन हो।
तो फिर ऊपर का कोड काम कैसे करता है?
इसका राज़ प्रोटोटाइप चेन कहलाता है।
जब आप किसी ऑब्जेक्ट की कोई भी प्रॉपर्टी (फील्ड या मेथड) पढ़ने की कोशिश करते हैं, तो JavaScript सबसे पहले देखती है कि वह की सीधे उसी ऑब्जेक्ट में मौजूद है या नहीं।
अगर नहीं, तो वह उस की को मूल ऑब्जेक्ट की [[prototype]] प्रॉपर्टी से जुड़े ऑब्जेक्ट में ढूँढती रहती है।
जैसा पहले बताया गया, हमारे उदाहरण में [[prototype]] कंस्ट्रक्टर फंक्शन की prototype प्रॉपर्टी की ओर इशारा करता है।
startEngine फंक्शन JavaScript को वहीं मिलेगा, क्योंकि हमने उसे वहीं जोड़ा था।
function Car() {
// ...
}
Car.prototype.startEngine = function () {
// ...
};
और चेन यहीं खत्म नहीं होती।
Car.prototype की [[prototype]] प्रॉपर्टी (myCar.[[prototype]].[[prototype]]) Object.prototype को रेफर करती है (यानी Object कंस्ट्रक्टर फंक्शन की prototype प्रॉपर्टी)।
इसमें वे आम मेथड होते हैं जो हर JavaScript ऑब्जेक्ट के लिए उपलब्ध हैं, जैसे toString()।
Object का [[prototype]] आम तौर पर null होता है, इसलिए प्रोटोटाइप चेन वहीं खत्म हो जाती है।
नतीजा यह है कि आप myCar.toString() कॉल कर सकते हैं, और वह मेथड मौजूद रहेगा क्योंकि JavaScript उसे पूरी प्रोटोटाइप चेन में ढूँढती है।
एक विस्तृत उदाहरण MDN का लेख "Inheritance and the prototype chain" में मिलेगा।
ध्यान रखिए कि प्रोटोटाइप चेन पर चलना सिर्फ तब होता है जब कोई वैल्यू पढ़ी जाती है। किसी इंस्टेंस ऑब्जेक्ट की प्रॉपर्टी सीधे सेट करना या मिटाना सिर्फ उसी इंस्टेंस पर असर डालता है। अगर आप क्लास-आधारित इनहेरिटेंस वाली भाषा के आदी हैं, तो यह आपकी अपेक्षा के उलट लग सकता है।
JavaScript आपको बने हुए सभी इंस्टेंस में बाद में भी मेथड जोड़ने देती है।
हमने सीखा कि हर इंस्टेंस कंस्ट्रक्टर फंक्शन की prototype प्रॉपर्टी का रेफरेंस रखता है।
इसका मतलब है कि अगर आप उस prototype ऑब्जेक्ट में कोई नई एंट्री जोड़ते हैं, तो वह नई एंट्री (जैसे कोई नया मेथड) उस कंस्ट्रक्टर फंक्शन से बने सभी इंस्टेंस के लिए तुरंत उपलब्ध हो जाती है।
function Car() {
this.engineRunning = false;
}
const myCar = new Car();
// Calling myCar.startEngine() here would result in "TypeError:
// myCar.startEngine is not a function".
Car.prototype.startEngine = function () {
this.engineRunning = true;
};
myCar.startEngine();
// This works, even though myCar was created before the method
// was added.
सिद्धांत में डायनामिक मेथड की मदद से Object या Array जैसे बिल्ट-इन ऑब्जेक्ट की कार्यक्षमता भी बढ़ाई जा सकती है, बस उनका प्रोटोटाइप बदलकर।
इसे मंकी पैचिंग कहते हैं।
चूँकि यह बदलाव पूरी ऐप्लिकेशन पर असर डालता है, इसलिए अनजाने साइड इफेक्ट से बचने के लिए इससे दूर रहना चाहिए।
इसका एक ही वाजिब इस्तेमाल है: पुराने वातावरणों में किसी गायब मेथड के लिए पॉलीफिल देना।
आजकल JavaScript में class कीवर्ड से क्लास बनाई जा सकती है।
यह 2015 में भाषा के स्पेसिफिकेशन में जोड़ा गया था।
एक तरफ, इससे सिंटैक्टिक शुगर मिलती है, जिससे क्लास पढ़ना और लिखना आसान हो जाता है।
नया सिंटैक्स C++ या Java जैसी भाषाओं में क्लास लिखने के तरीके से ज़्यादा मिलता-जुलता है।
उन भाषाओं से आने वाले डेवलपर के लिए ढलना आसान हो जाता है।
दूसरी तरफ, क्लास सिंटैक्स उन नए भाषा फीचर का रास्ता खोलता है जो प्रोटोटाइप सिंटैक्स में उपलब्ध नहीं हैं।
नए सिंटैक्स में क्लास class कीवर्ड से बनाई जाती है। इसके बाद क्लास का नाम आता है और फिर घुंघराले ब्रैकेट में क्लास बॉडी लिखी जाती है।
बॉडी में कंस्ट्रक्टर फंक्शन लिखा जाता है, यानी constructor नाम का एक खास मेथड।
यह फंक्शन प्रोटोटाइप सिंटैक्स वाले कंस्ट्रक्टर फंक्शन की तरह ही काम करता है।
क्लास बॉडी में सभी मेथड भी लिखे जाते हैं।
मेथड लिखने का सिंटैक्स उस शॉर्टहैंड तरीके जैसा है जो हमने ऑब्जेक्ट के अंदर फंक्शन को वैल्यू के रूप में जोड़ते समय देखा था। इसे ऑब्जेक्ट कॉन्सेप्ट में देखिए।
class Car {
constructor(color, weight) {
this.color = color;
this.weight = weight;
this.engineRunning = false;
}
startEngine() {
this.engineRunning = true;
}
addGas(litre) {
// ...
}
}
const myCar = new Car();
myCar.startEngine();
myCar.engineRunning;
// => true
फंक्शन डिक्लेरेशन और फंक्शन एक्सप्रेशन की तरह JavaScript ऊपर दिखाए गए क्लास डिक्लेरेशन के अलावा क्लास एक्सप्रेशन भी सपोर्ट करती है।
याद रखिए कि पर्दे के पीछे JavaScript अब भी प्रोटोटाइप-आधारित भाषा ही है। ऊपर "प्रोटोटाइप सिंटैक्स" वाले हिस्से में सीखी गई सभी बातें अब भी लागू होती हैं।
JavaScript में डिफॉल्ट रूप से सभी इंस्टेंस फील्ड पब्लिक होती हैं। इन्हें सीधे पढ़ा और असाइन किया जा सकता है।
भाषा के स्पेसिफिकेशन में असली प्राइवेट फील्ड जोड़ने का काम अभी चल रहा है। जानकारी के लिए प्रस्ताव दस्तावेज़ देखिए।
तब तक आप इस माने हुए नियम का इस्तेमाल कर सकते हैं कि अंडरस्कोर से शुरू होने वाली फील्ड और मेथड को प्राइवेट माना जाना चाहिए। इन्हें क्लास के बाहर से कभी सीधे नहीं पढ़ा जाना चाहिए।
प्राइवेट फील्ड के साथ कभी-कभी गेटर और सेटर भी होते हैं।
get और set कीवर्ड की मदद से आप ऐसे फंक्शन बना सकते हैं जो तब चलते हैं जब फंक्शन के नाम वाली प्रॉपर्टी पढ़ी या असाइन की जाती है।
class Car {
constructor() {
this._mileage = 0;
}
get mileage() {
return this._mileage;
}
set mileage(value) {
throw new Error(`Mileage cannot be manipulated, ${value} is ignored.`);
// Just an example, usually you would not provide a setter in this case.
}
}
const myCar = new Car();
myCar.mileage;
// => 0
myCar.mileage = 100;
// => Error: Mileage cannot be manipulated, 100 is ignored.
OOP में कभी-कभी आप ऐसी उपयोगी फील्ड या मेथड रखना चाहते हैं जो किसी खास इंस्टेंस पर निर्भर न हों।
बल्कि वे क्लास के लिए ही बनाए जाते हैं।
यह static कीवर्ड से किया जा सकता है।
class Car {
static type = 'vehicle';
static isType(targetType) {
return targetType === 'vehicle';
}
}
Car.type;
// => 'vehicle'
Car.isType('road sign');
// => false
पहले देखे गए प्रोटोटाइप चेन वाले इनहेरिटेंस के अलावा JavaScript में क्लास के बीच का इनहेरिटेंस भी दिखाया जा सकता है। इसके बारे में इनहेरिटेंस कॉन्सेप्ट में बताया गया है।
this के उदाहरण - ऑब्जेक्ट मेथड के रूप में, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#as_an_object_method (दिसंबर 03, 2021 को देखा गया) ↩