तुम्ही तीन फाइल्समधून बग शोधता आणि शेवटी असे लक्षात येते की एका साध्या हेल्पर फंक्शनने युजरचे नाव बदलले आहे. तुम्ही मूळ डेटाला स्पर्शही केला नव्हता. किंवा तुम्हाला तसे वाटले. JavaScript मध्ये, एका व्हेरिएबलला दुसऱ्या व्हेरिएबलमध्ये असाइन करणे म्हणजे नेहमी तुमच्या अपेक्षेप्रमाणे घडतेच असे नाही. ही भाषा आपल्या डेटाचे दोन स्टोरेज स्ट्रॅटेजीजमध्ये विभाजन करते, आणि तुम्ही नेमकी कोणती स्ट्रॅटेजी वापरत आहात हे विसरल्यामुळे प्रोडक्शन कोडमध्ये 'सायलेंट म्यूटेशन्स' (silent mutations) शिरतात.
हे टाळण्यासाठी, तुम्हाला प्रिमिटिव्ह व्हॅल्यूज (primitive values) आणि ऑब्जेक्ट्स (objects) मधील फरक अगदी इंजिनप्रमाणे समजून घेणे आवश्यक आहे.
Primitives: प्रत्यक्ष प्रती (Actual Copies)
प्रिमिटिव्ह म्हणजे एक एकल, अविभाज्य डेटा (datum) असतो. त्याचे लहान JavaScript व्हॅल्यूजमध्ये विभाजन करता येत नाही. ही भाषा सात प्रिमिटिव्ह प्रकार परिभाषित करते: String, Number, Boolean, Undefined, Null, Symbol, आणि BigInt.
प्रिमिटिव्ह हे अॅटॉमिक (atomic) असल्यामुळे, ते सामान्यतः थेट व्हेरिएबल बाइंडिंगमध्ये साठवले जातात. जेव्हा तुम्ही एक प्रिमिटिव्ह व्हेरिएबल दुसऱ्यामध्ये कॉपी करता, तेव्हा इंजिन प्रत्यक्ष डेटाची डुप्लिकेट प्रत तयार करते. प्रत्येक व्हेरिएबलला मेमरीमध्ये स्वतःची स्वतंत्र जागा मिळते.
let a = "Alina";
let b = a;
b = "Ali";
console.log(a); // "Alina"
console.log(b); // "Ali"
येथे, a मध्ये कोणताही बदल होत नाही. b ला पुन्हा असाइन केल्यामुळे एक नवीन व्हॅल्यू तयार झाली आणि b त्याकडे निर्देशित झाला, तर a कडे अजूनही त्याची मूळ स्ट्रिंग आहे. ही पद्धत 'copy by value' म्हणून ओळखली जाते. हीच पद्धत नंबर्स, बूलियन्स, सिम्बॉल्स आणि इतर सर्व प्रिमिटिव्ह प्रकारांसाठी सारखीच काम करते. तुम्ही त्यांना फंक्शन्समध्ये पास करू शकता, पुन्हा असाइन करू शकता किंवा इतर व्हेरिएबल्सवर कोणताही परिणाम न होता रिटर्न करू शकता.
Objects: सामायिक पत्ते (Shared Addresses)
ऑब्जेक्ट्स वेगळे असतात. ऑब्जेक्ट हा एक कंपोझिट कंटेनर असतो जो डेटाचे अनेक भाग एकत्र आणतो. या श्रेणीमध्ये प्लेन ऑब्जेक्ट्स, अॅरे (arrays), फंक्शन्स, डेट्स आणि इतर सर्व नॉन-प्रिमिटिव्ह प्रकारांचा समावेश होतो. हे स्ट्रक्चर्स मोठे आणि नेस्टेड (nested) असू शकतात, म्हणून JavaScript संपूर्ण ऑब्जेक्ट व्हेरिएबलमध्ये साठवत नाही. त्याऐवजी, व्हेरिएबलमध्ये एक 'रेफरन्स' (reference) असतो, जो प्रत्यक्षात इतरत्र साठवलेल्या मूळ डेटाकडे निर्देश करणारा मेमरी पत्ता असतो.
जेव्हा तुम्ही एखादा ऑब्जेक्ट नवीन व्हेरिएबलला असाइन करता, तेव्हा इंजिन पत्ता कॉपी करते, ऑब्जेक्ट नाही. आता दोन व्हेरिएबल्स एकाच घराकडे निर्देश करत आहेत.
const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";
console.log(user.name); // "Ali"
copy.name बदलल्यामुळे user.name देखील बदलला, कारण दोन्ही नावे एकाच मूळ ऑब्जेक्टला दर्शवतात. ही '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) खात्री देते. पण तसे नाही. ते फक्त रेफरन्स पुन्हा असाइन करण्यापासून रोखते. जर तुम्हाला त्यातील मजकूर सुरक्षित ठेवायचा असेल, तर तुम्हाला जाणीवपूर्वक कॉपी करावी लागेल.
खरे बग्स कुठे लपलेले असतात
रेफरन्स बग्स सहसा व्हेरिएबलच्या स्पष्ट री-असाइनमेंटमध्ये दिसत नाहीत. ते फंक्शन कॉल्सच्या आत लपलेले असतात.
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) परिणाम थेट कॉल करणाऱ्या ऑब्जेक्टवर झाला. ते फंक्शन एक साधे ट्रान्सफॉर्मेशन (transformation) वाटत होते, तरीही त्याने स्कोपच्या सीमा ओलांडून स्टेट लीक केली.
ही पद्धत React सारख्या UI फ्रेमवर्कमध्ये विशेषतः त्रासदायक ठरते, जिथे स्टेट अपडेट्स अवलंबून असतात