आप तीन फाइलों में एक बग को ट्रैक करते हैं, सिर्फ यह जानने के लिए कि एक हानिरहित हेल्पर फंक्शन ने यूजर का नाम बदल दिया है। आपने मूल (original) को कभी छुआ ही नहीं था। या कम से कम आपको ऐसा लगा। JavaScript में, एक वेरिएबल को दूसरे को असाइन करने का मतलब हमेशा वह नहीं होता जिसकी आप अपेक्षा करते हैं। यह भाषा अपने डेटा को दो स्टोरेज रणनीतियों (storage strategies) में विभाजित करती है, और यह भूल जाना कि आप किसका उपयोग कर रहे हैं, वही तरीका है जिससे साइलेंट म्यूटेशन (silent mutations) प्रोडक्शन कोड में घुस जाते हैं।
इसे रोकने के लिए, आपको प्रिमिटिव वैल्यूज़ (primitive values) और ऑब्जेक्ट्स (objects) के बीच के अंतर को ठीक उसी तरह देखने की आवश्यकता है जैसे इंजन देखता है।
Primitives: वास्तविक प्रतियां
एक प्रिमिटिव (primitive) एक एकल, अविभाज्य डेटा (datum) है। इसे छोटे JavaScript वैल्यूज़ में विभाजित नहीं किया जा सकता है। यह भाषा सात प्रिमिटिव प्रकारों को परिभाषित करती है: String, Number, Boolean, Undefined, Null, Symbol, और BigInt।
क्योंकि प्रिमिटिव्स एटॉमिक (atomic) होते हैं, वे आम तौर पर सीधे वेरिएबल बाइंडिंग (variable binding) के अंदर रहते हैं। जब आप एक प्रिमिटिव वेरिएबल को दूसरे में कॉपी करते हैं, तो इंजन वास्तविक डेटा को डुप्लिकेट कर देता है। प्रत्येक वेरिएबल को मेमोरी में अपना स्वतंत्र स्लॉट मिलता है।
let a = "Alina";
let b = a;
b = "Ali";
console.log(a); // "Alina"
console.log(b); // "Ali"
यहाँ, a अप्रभावित रहता है। b को पुन: असाइन (reassign) करने से एक बिल्कुल नया मान (value) बना और b को उसकी ओर निर्देशित किया गया, जबकि a अभी भी अपने मूल स्ट्रिंग को रखता है। यह व्यवहार 'copy by value' है। यह नंबरों, बूलियन, सिम्बल्स और प्रिमिटिव परिवार के बाकी हिस्सों के लिए भी समान रूप से काम करता है। आप उन्हें फंक्शन में पास कर सकते हैं, उन्हें पुन: असाइन कर सकते हैं, या बगल के वेरिएबल्स (sibling variables) पर साइड इफेक्ट्स की चिंता किए बिना उन्हें रिटर्न कर सकते हैं।
Objects: साझा पते
ऑब्जेक्ट्स अलग होते हैं। एक ऑब्जेक्ट एक कंपोजिट कंटेनर (composite container) है जो डेटा के कई हिस्सों को एक साथ समूहित करता है। इस श्रेणी में प्लेन ऑब्जेक्ट्स, एरेज़ (arrays), फंक्शन्स, डेट्स और अन्य सभी नॉन-प्रिमिटिव प्रकार शामिल हैं। क्योंकि ये संरचनाएं बड़ी और नेस्टेड (nested) हो सकती हैं, JavaScript पूरे ऑब्जेक्ट को वेरिएबल के अंदर स्टोर नहीं करता है। इसके बजाय, वेरिएबल एक रेफरेंस (reference) रखता है, जो अनिवार्य रूप से कहीं और स्टोर किए गए वास्तविक डेटा की ओर इशारा करने वाला एक मेमोरी एड्रेस है।
जब आप किसी ऑब्जेक्ट को एक नए वेरिएबल को असाइन करते हैं, तो इंजन एड्रेस को कॉपी करता है, ऑब्जेक्ट को नहीं। अब दो वेरिएबल्स बिल्कुल एक ही घर की ओर इशारा कर रहे हैं।
const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";
console.log(user.name); // "Ali"
copy.name को बदलने से user.name भी बदल गया क्योंकि दोनों नाम एक ही अंतर्निहित (underlying) ऑब्जेक्ट को दर्शाते हैं। यह 'copy by reference' है। यही आश्चर्य एरेज़ के साथ भी दिखाई देता है:
const scores = [82, 91, 74];
const backup = scores;
backup.push(88);
console.log(scores); // [82, 91, 74, 88]
मेमोरी में केवल एक ही एरे है। scores और backup केवल उसकी ओर इशारा करने वाले दो संकेत हैं।
const कीवर्ड भ्रम की एक परत जोड़ता है। const के साथ एक ऑब्जेक्ट घोषित करने से वेरिएबल बाइंडिंग लॉक हो जाती है ताकि आप इसे किसी नए पते पर पॉइंट न कर सकें। यह स्वयं ऑब्जेक्ट को लॉक नहीं करता है।
const settings = { theme: "dark" };
settings.theme = "light"; // Works perfectly.
settings = { theme: "dark" }; // TypeError
डेवलपर्स अक्सर उम्मीद करते हैं कि const इम्यूटेबिलिटी (immutability) का वादा करता है। यह ऐसा नहीं करता है। यह केवल रेफरेंस के पुन: असाइनमेंट को रोकता है। यदि आप सामग्री को सील (sealed) करना चाहते हैं, तो आपको इरादे के साथ कॉपी करना होगा।
असली बग्स कहाँ रहते हैं
रेफरेंस बग्स शायद ही कभी स्पष्ट वेरिएबल पुन: असाइनमेंट के रूप में सामने आते हैं। वे फंक्शन कॉल्स के अंदर छिपे होते हैं।
function addTimestamp(record) {
record.timestamp = Date.now();
return record;
}
const original = { id: 1 };
addTimestamp(original);
console.log(original.timestamp); // A number now exists here. Oops.
पैरामीटर record को रेफरेंस की एक कॉपी मिली। फंक्शन के अंदर किसी भी प्रॉपर्टी म्यूटेशन (property mutation) ने सीधे कॉलर (caller) के ऑब्जेक्ट में बदलाव कर दिया। फंक्शन एक साधारण ट्रांसफॉर्मेशन (transformation) जैसा लग रहा था, फिर भी इसने स्कोप की सीमाओं (scope boundaries) के पार स्टेट लीक कर दिया।
यह पैटर्न React जैसे UI फ्रेमवर्क में विशेष रूप से कष्टदायक है, जहाँ स्टेट अपडेट्स निर्भर करते हैं