Je spoort een bug op in drie verschillende bestanden, om er vervolgens achter te komen dat een onschuldige helperfunctie een gebruiker heeft hernoemd. Je hebt het origineel nooit aangeraakt. Of dat dacht je tenminste. In JavaScript betekent het toewijzen van de ene variabele aan de andere niet altijd wat je verwacht. De taal verdeelt zijn gegevens in twee opslagstrategieën, en het vergeten welke je gebruikt, is precies hoe stille mutaties in de productiecode sluipen.
Om dit te voorkomen, moet je het verschil tussen primitieve waarden en objecten zien precies zoals de engine dat doet.
Primitieven: Werkelijke kopieën
Een primitief is een enkel, ondeelbaar gegeven. Het kan niet worden ontleed in kleinere JavaScript-waarden. De taal definieert zeven primitieve types: String, Number, Boolean, Undefined, Null, Symbol en BigInt.
Omdat primitieven atomair zijn, bevinden ze zich over het algemeen direct in de variabele binding. Wanneer je een primitieve variabele naar een andere kopieert, dupliceert de engine de werkelijke gegevens. Elke variabele krijgt zijn eigen onafhankelijke plek in het geheugen.
let a = "Alina";
let b = a;
b = "Ali";
console.log(a); // "Alina"
console.log(b); // "Ali"
Hier blijft a onaangetast. Het opnieuw toewijzen van b creëerde een gloednieuwe waarde en wees b daarop, terwijl a nog steeds de oorspronkelijke string bevat. Dit gedrag is copy by value. Het werkt identiek voor numbers, booleans, symbols en de rest van de primitieve familie. Je kunt ze aan functies doorgeven, opnieuw toewijzen of retourneren zonder je zorgen te maken over bijwerkingen op andere variabelen.
Objecten: Gedeelde adressen
Objecten zijn anders. Een object is een samengestelde container die meerdere gegevens samenvoegt. Deze categorie omvat gewone objecten, arrays, functies, dates en elk ander niet-primitief type. Omdat deze structuren groot en genest kunnen worden, slaat JavaScript niet het hele object op in de variabele. In plaats daarvan bevat de variabele een referentie, in feite een geheugenadres dat naar de werkelijke gegevens wijst die elders zijn opgeslagen.
Wanneer je een object aan een nieuwe variabele toewijst, kopieert de engine het adres, niet het object. Twee variabelen wijzen nu naar exact hetzelfde huis.
const user = { name: "Alina" };
const copy = user;
copy.name = "Ali";
console.log(user.name); // "Ali"
Het wijzigen van copy.name veranderde ook user.name, omdat beide namen verwijzen naar hetzelfde onderliggende object. Dit is copy by reference. Dezelfde verrassing treedt op bij arrays:
const scores = [82, 91, 74];
const backup = scores;
backup.push(88);
console.log(scores); // [82, 91, 74, 88]
Er is slechts één array in het geheugen. scores en backup zijn slechts twee borden die ernaar wijzen.
Het const keyword voegt een laag verwarring toe. Het declareren van een object met const vergrendelt de variabele binding, zodat je deze niet naar een nieuw adres kunt wijzen. Het vergrendelt niet het object zelf.
const settings = { theme: "dark" };
settings.theme = "light"; // Works perfectly.
settings = { theme: "dark" }; // TypeError
Ontwikkelaars verwachten vaak dat const onveranderlijkheid belooft. Dat is niet zo. Het voorkomt alleen het opnieuw toewijzen van de referentie. Als je de inhoud wilt vastleggen, moet je met opzet kopiëren.
Waar de echte bugs zich bevinden
Referentie-bugs verschijnen zelden als duidelijke variabele toewijzingen. Ze verbergen zich in functieaanroepen.
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.
De parameter record ontving een kopie van de referentie. Elke mutatie van een eigenschap binnen de functie schreef rechtstreeks in het object van de aanroeper. De functie leek op een eenvoudige transformatie, maar lekte toch de state over scope-grenzen heen.
Dit patroon is vooral pijnlijk in UI-frameworks zoals React, waar state-updates afhankelijk zijn