Referințe și imutabilitate
Valoare vs referință, update-uri imutabile, shallow vs deep copy.
Actualizat
Ce înseamnă valoare și referință
Când atribui o valoare unei variabile, JS face unul din două lucruri, în funcție de tip:
| Prin valoare | Prin referință | |
|---|---|---|
| Tipuri | primitive: string, number, boolean, null, undefined | obiecte, array-uri, funcții |
| Ce se copiază | valoarea însăși | adresa spre aceeași valoare din memorie |
| Efect | două copii independente | două nume pentru același obiect |
let a = 1
let b = a
b = 2
a // 1 — copii independente
const x = { count: 1 }
const y = x // y indică același obiect ca x
y.count = 2
x.count // 2 — e același obiect!Analogie: primitivele sunt ca o fotocopie a unei foi; obiectele sunt ca un link spre un Google Doc — trimiți linkul, nu documentul.
Comparația urmează aceeași regulă
'a' === 'a' // true — compară valorile
{ a: 1 } === { a: 1 } // false — două obiecte diferite, adrese diferite
const o = {}; o === o // true — aceeași adresăCe este imutabilitatea
Imutabil = nu modifici obiectul existent, ci creezi unul nou cu modificarea. Opusul e mutația — modificarea pe loc.
// mutație
user.age = 31
// imutabil
const updated = { ...user, age: 31 }De ce contează — mai ales în React
React decide dacă să re-randeze comparând state-ul vechi cu cel nou prin referință (Object.is). Dacă modifici obiectul pe loc, referința e aceeași → React crede că nimic nu s-a schimbat → UI-ul nu se actualizează.
// ✗ mutație: aceeași referință, nicio re-randare
todos.push(newTodo)
setTodos(todos)
// ✓ array nou
setTodos([...todos, newTodo])Alte avantaje: poți compara rapid „s-a schimbat?”, ai istoric (undo), nu ai efecte ascunse între părți din cod care partajează același obiect.
Cheat-sheet: update-uri imutabile
| Operație | Array | Obiect |
|---|---|---|
| adaugă | [...arr, x] |
{ ...obj, key: v } |
| șterge | arr.filter(x => x.id !== id) |
const { key, ...rest } = obj |
| modifică unul | arr.map(x => x.id === id ? { ...x, done: true } : x) |
{ ...obj, key: v } |
| sortează | arr.toSorted(fn) |
— |
| la index | arr.with(i, v) |
— |
| nested | — | { ...obj, address: { ...obj.address, city } } |
Shallow copy vs deep copy
| Shallow (de suprafață) | Deep (completă) | |
|---|---|---|
| Cum | { ...obj }, [...arr], Object.assign |
structuredClone(obj) |
| Ce copiază | doar primul nivel; obiectele din interior rămân partajate | tot, la orice adâncime |
| Cost | mic | mai mare |
| Când | update-uri de state (copiezi doar ce schimbi) | când chiar ai nevoie de o copie independentă |
const copy = { ...user }
copy.address.city = 'Iași' // ✗ modifică și user.address — e același obiect
const deep = structuredClone(user)
deep.address.city = 'Iași' // ✓ user rămâne neatinsJSON.parse(JSON.stringify(x)) — hack-ul vechi; pierde Date, Map, undefined, funcții. Folosește structuredClone.
Object.freeze
Object.freeze(obj) blochează modificările (doar primul nivel). Util pentru constante de configurare, ca să prinzi mutații accidentale.
Pe scurt
- Primitivele se copiază prin valoare, obiectele prin referință.
- Imutabil = creezi obiect nou în loc să-l modifici; obligatoriu pentru state în React.
- Spread copiază doar primul nivel;
structuredClonepentru copie completă.