മൂന്ന് ഫയലുകളിലൂടെ ഒരു ബഗ് തിരഞ്ഞുകണ്ടെത്തിയപ്പോൾ, ഒരു നിസ്സാരമായ ഹെൽപ്പർ ഫംഗ്ഷൻ ഒരു ഉപയോക്താവിന്റെ പേര് മാറ്റിയതാണെന്ന് നിങ്ങൾ കണ്ടെത്തുന്നു. നിങ്ങൾ ഒറിജിനൽ ഫയലിൽ മാറ്റങ്ങളൊന്നും വരുത്തിയിരുന്നില്ല. അല്ലെങ്കിൽ നിങ്ങൾ അങ്ങനെ കരുതി. JavaScript-ൽ, ഒരു വേരിയബിളിനെ മറ്റൊരു വേരിയബിളിന് അസൈൻ ചെയ്യുന്നത് നിങ്ങൾ പ്രതീക്ഷിക്കുന്നത് പോലെ എപ്പോഴും ഉണ്ടാകണമെന്നില്ല. ഈ ഭാഷ ഡാറ്റയെ രണ്ട് സ്റ്റോറേജ് സ്ട്രാറ്റജികളായി തിരിക്കുന്നു, ഇതിൽ ഏതാണ് നിങ്ങൾ ഉപയോഗിക്കുന്നത് എന്ന് മറന്നുപോകുന്നത് പ്രൊഡക്ഷൻ കോഡുകളിൽ നിശബ്ദമായ മാറ്റങ്ങൾ (silent mutations) ഉണ്ടാകാൻ കാരണമാകുന്നു.
ഇത് തടയുന്നതിന്, പ്രിമിറ്റീവ് വാല്യൂസുകളും (primitive values) ഒബ്ജക്റ്റുകളും (objects) തമ്മിലുള്ള വ്യത്യാസം എഞ്ചിൻ കാണുന്നതുപോലെ തന്നെ നിങ്ങൾ മനസ്സിലാക്കേണ്ടതുണ്ട്.
Primitives: Actual Copies
ഒരു പ്രിമിറ്റീവ് എന്നത് ഒറ്റപ്പെട്ടതും വിഭജിക്കാൻ കഴിയാത്തതുമായ ഒരു ഡാറ്റയാണ്. ഇതിനെ ചെറിയ 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 എന്ന് വിളിക്കുന്നു. ഇത് നമ്പറുകൾ, ബൂളിയനുകൾ, സിംബലുകൾ, മറ്റ് പ്രിമിറ്റീവ് ടൈപ്പുകൾ എന്നിവയ്ക്കും ഒരുപോലെ പ്രവർത്തിക്കുന്നു. മറ്റ് വേരിയബിളുകളിൽ side effects ഉണ്ടാകുമോ എന്ന് ആശങ്കപ്പെടാതെ തന്നെ നിങ്ങൾക്ക് ഇവ ഫംഗ്ഷനുകളിലേക്ക് പാസ് ചെയ്യാനോ, റീ-അസൈൻ ചെയ്യാനോ, അല്ലെങ്കിൽ റിട്ടേൺ ചെയ്യാനോ കഴിയും.
Objects: Shared Addresses
ഒബ്ജക്റ്റുകൾ വ്യത്യസ്തമാണ്. ഒന്നിലധികം ഡാറ്റാ ഭാഗങ്ങളെ ഒരുമിച്ച് ഗ്രൂപ്പ് ചെയ്യുന്ന ഒരു കോമ്പോസിറ്റ് കണ്ടെയ്നറാണ് ഒബ്ജക്റ്റ്. പ്ലെയിൻ ഒബ്ജക്റ്റുകൾ, അറേകൾ (arrays), ഫംഗ്ഷനുകൾ, ഡേറ്റുകൾ (dates), മറ്റ് എല്ലാ നോൺ-പ്രിമിറ്റീവ് ടൈപ്പുകളും ഈ വിഭാഗത്തിൽ ഉൾപ്പെടുന്നു. ഈ ഘടനകൾ വലുതാകാനും നെസ്റ്റഡ് (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) എന്ന് ഡെവലപ്പർമാർ പലപ്പോഴും പ്രതീക്ഷിക്കാറുണ്ട്. എന്നാൽ അത് അങ്ങനെയല്ല. ഇത് റഫറൻസ് റീ-അസൈൻ ചെയ്യുന്നത് മാത്രമാണ് തടയുന്നത്. ഒബ്ജക്റ്റിലെ ഉള്ളടക്കം മാറ്റമില്ലാതെ നിലനിർത്തണമെങ്കിൽ, നിങ്ങൾ ബോധപൂർവ്വം കോപ്പി ചെയ്യേണ്ടതുണ്ട്.
Where the Real Bugs Live
റഫറൻസ് ബഗുകൾ സാധാരണയായി വേരിയബിൾ റീ-അസൈൻമെന്റുകളായി പ്രത്യക്ഷപ്പെടാറില്ല. അവ ഫംഗ്ഷൻ കോളുകൾക്കുള്ളിൽ ഒളിച്ചിരിക്കുന്നു.
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) നേരിട്ട് കോൾ ചെയ്ത ഒബ്ജക്റ്റിലേക്ക് എഴുതപ്പെടുന്നു. ഫംഗ്ഷൻ ഒരു ലളിതമായ മാറ്റം മാത്രമാണെന്ന് തോന്നിച്ചേക്കാം, എന്നാൽ അത് സ്കോപ്പ് അതിരുകൾക്ക് പുറത്തേക്ക് സ്റ്റേറ്റ് ചോർത്തുന്നു (leaked state).
React പോലുള്ള UI ഫ്രെയിംവർക്കുകളിൽ ഈ രീതി വളരെ ബുദ്ധിമുട്ടുള്ളതാണ്, അവിടെ സ്റ്റേറ്റ് അപ്ഡേറ്റുകൾ ആശ്രയിക്കുന്നത്