सोर्स कोड का अध्ययन निस्संदेह आपके डेवलपर करियर की दिशा बदल सकता है। सतह के नीचे सिर्फ़ एक स्तर पर देखने से ही आप ज़्यादातर औसत डेवलपर्स से अलग हो सकते हैं।
यह निपुणता की ओर पहला कदम है!
यहाँ एक व्यक्तिगत कहानी है: एक AI/ML स्टार्टअप में मेरे वर्तमान काम में, टीम यह पता नहीं लगा पाई कि विज़ुअलाइज़ेशन के लिए सर्वर से फ्रंटएंड तक Neo4j डेटा कैसे लाया जाए, और उन्हें 12 घंटे में एक प्रेजेंटेशन देना था। मुझे एक फ्रीलांसर के रूप में लाया गया था, और आप स्पष्ट रूप से घबराहट देख सकते थे। समस्या यह थी कि Neo4j द्वारा लौटाया गया डेटा विज़ुअलाइज़ेशन टूल, neo4jd3 द्वारा अपेक्षित सही प्रारूप में नहीं था।
कल्पना करें: Neo4jd3 एक त्रिभुज की अपेक्षा करता है, और Neo4j एक वर्ग लौटाता है। यह एक असंगत बेमेल है!
हम जल्द ही मास्टर्ड में जावास्क्रिप्ट और नियो4जे के साथ ग्राफ डेटा साइंस कर सकते हैं! यह छवि पुरानी यादों को ताजा करती है।
केवल दो विकल्प थे: संपूर्ण Neo4j बैकएंड को फिर से तैयार करना या Neo4jd3 के स्रोत कोड का अध्ययन करना, अपेक्षित प्रारूप का पता लगाना, और फिर वर्ग को त्रिभुज में बदलने के लिए एक एडाप्टर बनाना।
neo4jd3 <- adapter <- Neo4j
मेरा मस्तिष्क स्रोत कोड पढ़ने में लग गया, और मैंने एक एडाप्टर बनाया: neo4jd3-ts ।
import createNeoChart, { NeoDatatoChartData } from "neo4jd3-ts";
एडाप्टर NeoDatatoChartData
है, और बाकी सब इतिहास है। मैंने इस सबक को दिल से लिया, और जब भी मुझे मौका मिलता है, मैं हर उस टूल में एक स्तर नीचे चला जाता हूँ जिसका मैं उपयोग करता हूँ। यह इतना प्रचलित हो गया है कि कभी-कभी मैं दस्तावेज़ भी नहीं पढ़ता।
इस दृष्टिकोण ने मेरे करियर को बहुत बदल दिया। मैं जो कुछ भी करता हूँ वह जादू जैसा लगता है। कुछ ही महीनों में, मैं महत्वपूर्ण सर्वर माइग्रेशन और प्रोजेक्ट्स का नेतृत्व कर रहा था, यह सब इसलिए क्योंकि मैंने सोर्स की ओर एक कदम बढ़ाया था।
यह श्रृंखला इसी बारे में है: API से संतुष्ट न होना, बल्कि उससे आगे बढ़कर, इन उपकरणों को फिर से बनाना सीखना। AI हाइप की इस दुनिया में औसत से बाहर निकलना ही एक डेवलपर को औसत से कहीं ज़्यादा मूल्यवान बनाता है!
इस श्रृंखला के साथ मेरी योजना लोकप्रिय जावास्क्रिप्ट लाइब्रेरीज़ और टूल्स का अध्ययन करना है, तथा एक समय में एक टूल के माध्यम से यह पता लगाना है कि वे किस प्रकार काम करते हैं और हम उनसे क्या पैटर्न सीख सकते हैं।
चूंकि मैं मुख्यतः एक बैकएंड इंजीनियर हूं (हां, पूर्ण स्टैक, लेकिन 90% समय बैकएंड को संभालता हूं), इसलिए शुरुआत करने के लिए Express.js से बेहतर कोई टूल नहीं है।
मेरा अनुमान है कि आपको प्रोग्रामिंग का अनुभव है और प्रोग्रामिंग के मूल सिद्धांतों की अच्छी समझ है! आपको एक उन्नत शुरुआती के रूप में वर्गीकृत किया जा सकता है।
बुनियादी बातों को पढ़ाते समय सोर्स कोड सीखना/सिखाना वाकई बहुत कठिन और थकाऊ होगा। आप इस सीरीज़ में शामिल हो सकते हैं, लेकिन यह कठिन होने की उम्मीद करें। मैं सब कुछ कवर नहीं कर सकता, लेकिन मैं जितना हो सके उतना प्रयास करूँगा।
यह लेख एक कारण से एक्सप्रेस से पहले का है: मैंने एक बहुत छोटी लाइब्रेरी, मर्ज-डिस्क्रिप्टर्स , जिस पर एक्सप्रेस निर्भर है, को कवर करने का निर्णय लिया, जिसके, जब मैं यह लिख रहा हूँ, 27,181,495 डाउनलोड हैं और कोड की मात्र 26 लाइनें हैं।
इससे हमें एक संरचना स्थापित करने का अवसर मिलेगा और मुझे ऑब्जेक्ट के मूल सिद्धांतों को प्रस्तुत करने में मदद मिलेगी जो जावास्क्रिप्ट मॉड्यूल के निर्माण में महत्वपूर्ण हैं।
आगे बढ़ने से पहले, सुनिश्चित करें कि आपके सिस्टम में एक्सप्रेस सोर्स कोड और मर्ज-डिस्क्रिप्टर मौजूद हैं। इस तरह, आप इसे IDE में खोल सकते हैं और मैं आपको लाइन नंबर के साथ बता सकता हूँ कि हम कहाँ देख रहे हैं।
एक्सप्रेस एक बहुत बड़ी लाइब्रेरी है। हम दूसरे टूल पर जाने से पहले कुछ लेखों में जितना संभव हो उतना कवर करेंगे।
अपने IDE में एक्सप्रेस स्रोत खोलें, अधिमानतः लाइन नंबर के साथ, lib
फ़ोल्डर पर जाएँ, और express.js
फ़ाइल, प्रविष्टि फ़ाइल खोलें।
लाइन 17 पर हमारी पहली लाइब्रेरी है:
var mixin = require('merge-descriptors');
उपयोग पंक्ति 42 और 43 में है:
mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
इससे पहले कि हम यह पता लगाएँ कि यहाँ क्या हो रहा है, हमें एक कदम पीछे हटकर जावास्क्रिप्ट में ऑब्जेक्ट्स के बारे में बात करनी होगी, डेटा संरचना से परे। हम कंपोजिशन, इनहेरिटेंस, प्रोटोटाइप और मिक्सिन पर चर्चा करेंगे - जो इस लेख का शीर्षक है।
एक्सप्रेस स्रोत कोड को बंद करें, और कहीं नया फ़ोल्डर बनाएं ताकि हम इन महत्वपूर्ण ऑब्जेक्ट मूल सिद्धांतों को सीखते समय उसका अनुसरण कर सकें।
ऑब्जेक्ट, डेटा और व्यवहार का एक समामेलन है, जो ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग (OOP) के मूल में है। मज़ेदार तथ्य: जावास्क्रिप्ट में लगभग सब कुछ एक ऑब्जेक्ट है।
const person = { // data name: "Jane", age: 0, // behavior grow(){ this.age += 1; } };
person
ऑब्जेक्ट में उद्घाटन और समापन ब्रेसिज़ के बीच की हर चीज़ को ऑब्जेक्ट की अपनी प्रॉपर्टीज़ कहा जाता है। यह महत्वपूर्ण है।
स्वयं के गुण वे हैं जो सीधे वस्तु पर होते हैं। name
, age
और grow
person
के स्वयं के गुण हैं।
यह महत्वपूर्ण है क्योंकि प्रत्येक जावास्क्रिप्ट ऑब्जेक्ट में एक prototype
प्रॉपर्टी होती है। आइए उपरोक्त ऑब्जेक्ट को फ़ंक्शन ब्लूप्रिंट में एनकोड करें ताकि हम गतिशील रूप से person
ऑब्जेक्ट बना सकें।
function createNewPerson(name, age){ this.name = name; this.age = age; } createNewPerson.prototype.print = function(){ console.log(`${this.name} is ${this.age}`); }; const john = new createNewPerson("John", 32);
प्रोटोटाइप वह तरीका है जिससे जावास्क्रिप्ट ऑब्जेक्ट अन्य ऑब्जेक्ट से गुण और विधियाँ प्राप्त करते हैं। Own Properties
और Prototype
के बीच अंतर तब होता है जब किसी ऑब्जेक्ट पर प्रॉपर्टी एक्सेस की जाती है:
john.name; // access
जावास्क्रिप्ट सबसे पहले Own Properties
देखेगा, क्योंकि वे उच्च प्राथमिकता लेते हैं। यदि उसे गुण नहीं मिलता है, तो वह ऑब्जेक्ट के अपने prototype
ऑब्जेक्ट को पुनरावर्ती रूप से तब तक देखता है जब तक कि उसे शून्य न मिल जाए और एक त्रुटि न हो जाए।
एक प्रोटोटाइप ऑब्जेक्ट अपने स्वयं के प्रोटोटाइप के माध्यम से किसी अन्य ऑब्जेक्ट से विरासत प्राप्त कर सकता है। इसे प्रोटोटाइप चेन कहा जाता है।
console.log(john.hasOwnProperty('name')); // true console.log(john.hasOwnProperty('print')); // false, it's in the prototype
हालाँकि, print
john
पर काम करता है:
john.print(); // "John is 32"
यही कारण है कि जावास्क्रिप्ट को प्रोटोटाइप-आधारित भाषा के रूप में परिभाषित किया गया है। हम प्रोटोटाइप के साथ सिर्फ़ गुण और विधियाँ जोड़ने के अलावा और भी बहुत कुछ कर सकते हैं, जैसे कि विरासत।
विरासत की "हेलो वर्ल्ड" स्तनधारी ऑब्जेक्ट है। आइए इसे जावास्क्रिप्ट के साथ फिर से बनाएँ।
// our Mammal blueprint function Mammal(name) { this.name = name; } Mammal.prototype.breathe = function() { console.log(`${this.name} is breathing.`); };
जावास्क्रिप्ट में, Object
ऑब्जेक्ट के अंदर एक स्थिर फ़ंक्शन होता है:
Object.create();
यह {}
और new functionBlueprint
के समान ही एक ऑब्जेक्ट बनाता है, लेकिन अंतर यह है कि create
एक प्रोटोटाइप को इनहेरिट करने के लिए पैरामीटर के रूप में ले सकता है।
// we use a cat blueprint function here (implemented below) Cat.prototype = Object.create(Mammal.prototype); // correction after we inherited all the properties Cat.prototype.constructor = Cat;
अब Cat
में Mammal
में पाई जाने वाली breathe
विधि होगी, लेकिन जानने वाली महत्वपूर्ण बात यह है कि Cat
अपने प्रोटोटाइप के रूप में Mammal
ओर इशारा कर रही है।
स्तनधारी ब्लूप्रिंट : हम सबसे पहले Mammal
फ़ंक्शन को परिभाषित करते हैं और इसके प्रोटोटाइप में breathe
विधि जोड़ते हैं।
कैट इनहेरिटेंस : हम Cat
फ़ंक्शन बनाते हैं और Cat.prototype
को Object.create(Mammal.prototype)
पर सेट करते हैं। यह Cat
प्रोटोटाइप को Mammal
से इनहेरिट करता है, लेकिन यह constructor
पॉइंटर को Mammal
में बदल देता है।
कंस्ट्रक्टर को सही करना : हम Cat.prototype.constructor
Cat
पर वापस इंगित करने के लिए सही करते हैं, यह सुनिश्चित करते हुए कि Mammal
से विधियों को इनहेरिट करते समय Cat
ऑब्जेक्ट अपनी पहचान बनाए रखता है। अंत में, हम Cat
में एक meow
विधि जोड़ते हैं।
यह दृष्टिकोण Cat
ऑब्जेक्ट को Mammal
(जैसे breathe
) और इसके स्वयं के प्रोटोटाइप (जैसे meow
) दोनों से विधियों तक पहुंचने की अनुमति देता है।
हमें इसे ठीक करने की ज़रूरत है। आइए पूरा उदाहरण बनाएं:
function Cat(name, breed) { this.name = name; this.breed = breed; } Cat.prototype = Object.create(Mammal.prototype); // cat prototype pointing to mammal // correction after we inherited all the properties Cat.prototype.constructor = Cat; // we are re-pointing a pointer, the inherited properties are still there Cat.prototype.meow = function() { console.log(`${this.name} is meowing.`); };
Cat.prototype.constructor = Cat
समझने के लिए, आपको पॉइंटर्स के बारे में जानना होगा। जब हम Object.create
के साथ Mammal
से इनहेरिट करते हैं, तो यह हमारे Cat
प्रोटोटाइप के पॉइंटर को Mammal
में बदल देता है, जो गलत है। हम अभी भी चाहते हैं कि हमारा Cat
अपना अलग व्यक्तित्व हो, भले ही उसका पैरेंट Mammal
हो।
इसलिए हमें इसे ठीक करना होगा।
इस उदाहरण में, Cat
प्रोटोटाइप चेन का उपयोग करके Mammal
से विरासत में प्राप्त होता है। Cat
ऑब्जेक्ट breathe
और meow
दोनों विधियों तक पहुँच सकता है।
const myCat = new Cat("Misty", "Ragdoll"); myCat.breathe(); // Misty is breathing. myCat.meow(); // Misty is meowing.
हम स्तनपायी से विरासत में प्राप्त एक कुत्ता भी बना सकते हैं:
function Dog(name, breed) { this.name = name; this.breed = breed; } Dog.prototype = Object.create(Mammal.prototype); Dog.prototype.constructor = Dog; Dog.prototype.bark = function() { console.log(`${this.name} is barking.`); }; const myDog = new Dog('Buddy', 'Golden Retriever'); myDog.breathe(); // Buddy is breathing. myDog.bark(); // Buddy is barking.
हमने बुनियादी शास्त्रीय विरासत बनाई है, लेकिन यह महत्वपूर्ण क्यों है? मुझे लगा कि हम स्रोत कोड को कवर कर रहे थे!
हां, यह सच है, लेकिन प्रोटोटाइप विरासत से परे कुशल और लचीले मॉड्यूल बनाने का मूल है। यहां तक कि सरल, अच्छी तरह से लिखे गए मॉड्यूल भी प्रोटोटाइप ऑब्जेक्ट से भरे होते हैं। हम बस मूल बातें बता रहे हैं।
वंशानुक्रम का विकल्प वस्तु संयोजन है, जो दो या अधिक वस्तुओं को लेकर उन्हें एक साथ मिलाकर एक "सुपर" वस्तु बनाता है।
मिक्सिन ऑब्जेक्ट को इनहेरिटेंस का उपयोग किए बिना अन्य ऑब्जेक्ट से विधियाँ उधार लेने की अनुमति देते हैं। वे असंबंधित ऑब्जेक्ट के बीच व्यवहार साझा करने के लिए उपयोगी हैं।
यही हमारा पहला अन्वेषण है: merge-descriptors
लाइब्रेरी, जिसे हमने पहले कवर करने का वादा किया था।
हम पहले ही देख चुके हैं कि एक्सप्रेस में इसका उपयोग कहाँ और कैसे किया जाता है। अब हम इसे ऑब्जेक्ट कंपोजिशन के लिए जानते हैं।
पंक्ति 17 में हमारी पहली लाइब्रेरी है:
var mixin = require('merge-descriptors');
उपयोग पंक्ति 42 और 43 में है:
mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
जो हम जानते हैं, उसके आधार पर हम पहले ही यह निष्कर्ष निकाल सकते हैं कि mixin
EventEmitter.prototype
और proto
app
नामक ऑब्जेक्ट में संयोजित कर रहा है।
जब हम एक्सप्रेस के बारे में बात करना शुरू करेंगे तो हम app
पर आएंगे।
यह merge-descriptors
के लिए संपूर्ण स्रोत कोड है:
'use strict'; function mergeDescriptors(destination, source, overwrite = true) { if (!destination) { throw new TypeError('The `destination` argument is required.'); } if (!source) { throw new TypeError('The `source` argument is required.'); } for (const name of Object.getOwnPropertyNames(source)) { if (!overwrite && Object.hasOwn(destination, name)) { // Skip descriptor continue; } // Copy descriptor const descriptor = Object.getOwnPropertyDescriptor(source, name); Object.defineProperty(destination, name, descriptor); } return destination; } module.exports = mergeDescriptors;
शुरुआत से ही, हमेशा देखें कि फ़ंक्शन का उपयोग कैसे किया जाता है और यह कौन से पैरामीटर लेता है:
// definition mergeDescriptors(destination, source, overwrite = true) // usage var mixin = require('merge-descriptors'); mixin(app, EventEmitter.prototype, false); mixin(app, proto, false);
App
हमारा गंतव्य है। हम जानते हैं कि mixin
का मतलब ऑब्जेक्ट कंपोजिशन है। मोटे तौर पर, यह पैकेज जो कर रहा है वह स्रोत ऑब्जेक्ट को गंतव्य ऑब्जेक्ट में कंपोज़ कर रहा है, जिसमें ओवरराइट करने का विकल्प भी है।
अधिलेखन, धारणा के अनुसार, यदि app
(गंतव्य) में एक सटीक गुण है, तो स्रोत में भी वही गुण है, true
अधिलेखन पर, अन्यथा उस गुण को अछूता छोड़ दें और छोड़ दें।
हम जानते हैं कि ऑब्जेक्ट में एक ही गुण दो बार नहीं हो सकता। कुंजी-मूल्य युग्मों (ऑब्जेक्ट) में, कुंजियाँ अद्वितीय होनी चाहिए।
एक्सप्रेस के साथ, अधिलेखित false
है।
निम्नलिखित बुनियादी हाउसकीपिंग है, हमेशा अपेक्षित त्रुटियों को संभालें:
if (!destination) { throw new TypeError('The `destination` argument is required.'); } if (!source) { throw new TypeError('The `source` argument is required.'); }
यहाँ से यह दिलचस्प हो जाता है: पंक्ति 12.
for (const name of Object.getOwnPropertyNames(source)) {
ऊपर से, हम जानते हैं कि OwnProperty
क्या अर्थ है, इसलिए getOwnPropertyNames
स्पष्ट अर्थ है स्वयं की संपत्तियों की कुंजी प्राप्त करना।
const person = { // data name: "Jane", age: 0, // behavior grow() { this.age += 1; } }; Object.getOwnPropertyNames(person); // [ 'name', 'age', 'grow' ]
यह कुंजियों को एक सारणी के रूप में लौटाता है, और हम निम्नलिखित उदाहरण में उन कुंजियों पर लूपिंग कर रहे हैं:
for (const name of Object.getOwnPropertyNames(source)) {
निम्नलिखित जाँच यह है कि क्या गंतव्य और स्रोत में वही कुंजी है जिस पर हम वर्तमान में लूपिंग कर रहे हैं:
if (!overwrite && Object.hasOwn(destination, name)) { // Skip descriptor continue; }
अगर ओवरराइट गलत है, तो उस प्रॉपर्टी को छोड़ दें; ओवरराइट न करें। continue
यही करता है - यह लूप को अगले पुनरावृत्ति पर ले जाता है और नीचे दिए गए कोड को नहीं चलाता है, जो निम्न कोड है:
// Copy descriptor const descriptor = Object.getOwnPropertyDescriptor(source, name); Object.defineProperty(destination, name, descriptor);
हम पहले से ही जानते हैं कि getOwnProperty
क्या मतलब है। नया शब्द है descriptor
। आइए इस फ़ंक्शन को अपने स्वयं के person
ऑब्जेक्ट पर परीक्षण करें:
const person = { // data name: "Jane", age: 0, // behavior grow() { this.age += 1; } }; Object.getOwnPropertyDescriptor(person, "grow"); // { // value: [Function: grow], // writable: true, // enumerable: true, // configurable: true // }
यह हमारे grow
फ़ंक्शन को मान के रूप में लौटाता है, और अगली पंक्ति स्वतः स्पष्ट है:
Object.defineProperty(destination, name, descriptor);
यह हमारे डिस्क्रिप्टर को सोर्स से लेकर डेस्टिनेशन में लिख रहा है। यह सोर्स के अपने गुणों को हमारे डेस्टिनेशन ऑब्जेक्ट में अपने गुणों के रूप में कॉपी कर रहा है।
आइये अपने person
ऑब्जेक्ट में एक उदाहरण लेते हैं:
const val = { value: function isAlien() { return false; }, enumerable: true, writable: true, configurable: true, }; Object.defineProperty(person, "isAlien", val);
अब person
पास isAlien
गुण परिभाषित होना चाहिए।
संक्षेप में, यह अत्यधिक डाउनलोड किया जाने वाला मॉड्यूल स्रोत ऑब्जेक्ट से गंतव्य में स्वयं के गुणों की प्रतिलिपि बनाता है, जिसमें अधिलेखित करने का विकल्प भी होता है।
हमने इस स्रोत कोड स्तर में अपना पहला मॉड्यूल सफलतापूर्वक कवर कर लिया है, और आगे और भी रोमांचक चीजें आने वाली हैं।
यह एक परिचय था। हमने मॉड्यूल को समझने के लिए आवश्यक मूलभूत बातों को कवर करके शुरुआत की और, एक उपोत्पाद के रूप में, अधिकांश मॉड्यूल में पैटर्न को समझा, जो कि ऑब्जेक्ट संरचना और विरासत है। अंत में, हमने merge-descriptors
मॉड्यूल को नेविगेट किया।
यह पैटर्न ज़्यादातर लेखों में प्रचलित होगा। अगर मुझे लगता है कि कवर करने के लिए ज़रूरी बुनियादी बातें हैं, तो हम पहले सेक्शन में उन पर चर्चा करेंगे और फिर सोर्स कोड को कवर करेंगे।
सौभाग्य से, merge-descriptors
उपयोग एक्सप्रेस में किया जाता है, जो हमारे शुरुआती स्रोत कोड अध्ययन के रूप में हमारा ध्यान केंद्रित है। इसलिए जब तक हमें नहीं लगता कि हमने एक्सप्रेस का पर्याप्त अच्छा उपयोग कर लिया है, तब तक और अधिक Express.js स्रोत कोड लेखों की अपेक्षा करें, फिर Node.js जैसे किसी अन्य मॉड्यूल या टूल पर स्विच करें।
इस बीच आप जो कर सकते हैं वह है मर्ज डिस्क्रिप्टर में टेस्ट फ़ाइल पर नेविगेट करना, पूरी फ़ाइल को पढ़ना। अपने आप स्रोत को पढ़ना महत्वपूर्ण है, कोशिश करें और पता लगाएं कि यह क्या करता है और परीक्षण कर रहा है फिर इसे तोड़ दें, हाँ और इसे फिर से ठीक करें या अधिक परीक्षण जोड़ें!
यदि आप अपने प्रोग्रामिंग कौशल को बढ़ाने के लिए अधिक विशिष्ट व्यावहारिक और लंबी सामग्री में रुचि रखते हैं, तो आप को-फाई पर अधिक जानकारी पा सकते हैं।