[Medium] π Tipi primitivi vs tipi di riferimento
1. Cosa sono i tipi primitivi e i tipi di riferimento?β
Cosa sono i tipi primitivi e i tipi di riferimento?
I tipi di dati in JavaScript possono essere raggruppati in due categorie: tipi primitivi e tipi di riferimento. Differiscono fondamentalmente nel comportamento della memoria e nella semantica di passaggio.
Tipi primitivi (Primitive Types)β
Caratteristiche:
- Memorizzati come valori diretti (comunemente concettualizzati nello stack)
- Passati per copia del valore
- Immutabili
7 tipi primitivi:
// 1. String
const str = 'hello';
// 2. Number
const num = 42;
// 3. Boolean
const bool = true;
// 4. Undefined
let undef;
// 5. Null
const n = null;
// 6. Symbol (ES6)
const sym = Symbol('unique');
// 7. BigInt (ES2020)
const bigInt = 9007199254740991n;
Tipi di riferimento (Reference Types)β
Caratteristiche:
- Gli oggetti sono allocati nella memoria heap
- Le variabili contengono riferimenti (indirizzi)
- Mutabili
Esempi:
// 1. Object
const obj = { name: 'John' };
// 2. Array
const arr = [1, 2, 3];
// 3. Function
const func = function () {};
// 4. Date
const date = new Date();
// 5. RegExp
const regex = /abc/;
// 6. Map, Set, WeakMap, WeakSet (ES6)
const map = new Map();
const set = new Set();
2. Call by Value vs Call by Referenceβ
Call by Value vs Call by Reference
Call by Value (comportamento dei primitivi)β
Comportamento: il valore viene copiato; modificare la copia non influenza l'originale.
let a = 10;
let b = a; // copia il valore
b = 20;
console.log(a); // 10
console.log(b); // 20
Diagramma della memoria:
βββββββββββ
β Stack β
βββββββββββ€
β a: 10 β <- valore indipendente
βββββββββββ€
β b: 20 β <- valore indipendente dopo copia/aggiornamento
βββββββββββ
Comportamento dei riferimenti (oggetti)β
Comportamento: il riferimento viene copiato; entrambe le variabili possono puntare allo stesso oggetto.
let obj1 = { name: 'John' };
let obj2 = obj1; // copia il riferimento
obj2.name = 'Jane';
console.log(obj1.name); // 'Jane'
console.log(obj2.name); // 'Jane'
console.log(obj1 === obj2); // true
Diagramma della memoria:
βββββββββββ ββββββββββββββββββββ
β Stack β β Heap β
βββββββββββ€ ββββββββββββββββββββ€
β obj1 ββββΌβββββββββββββββββββ>β { name: 'Jane' } β
βββββββββββ€ β β
β obj2 ββββΌβββββββββββββββββββ>β (stesso oggetto) β
βββββββββββ ββββββββββββββββββββ
3. Domande quiz comuni (Common Quiz Questions)β
Domande quiz comuni
Domanda 1: passaggio di valori primitiviβ
function changeValue(x) {
x = 100;
console.log('Dentro la funzione x:', x);
}
let num = 50;
changeValue(num);
console.log('Fuori dalla funzione num:', num);
Clicca per vedere la risposta
// Dentro la funzione x: 100
// Fuori dalla funzione num: 50
Spiegazione:
numè un primitivo (Number)- l'argomento della funzione ottiene una copia del valore
- modificare
xnon modificanum
// flusso
let num = 50; // Stack: num = 50
changeValue(num); // Stack: x = 50 (copia)
x = 100; // solo x cambia
console.log(num); // ancora 50
Domanda 2: passaggio di oggettiβ
function changeObject(obj) {
obj.name = 'Changed';
console.log('Dentro la funzione obj.name:', obj.name);
}
let person = { name: 'Original' };
changeObject(person);
console.log('Fuori dalla funzione person.name:', person.name);
Clicca per vedere la risposta
// Dentro la funzione obj.name: Changed
// Fuori dalla funzione person.name: Changed
Spiegazione:
personè un tipo di riferimento (Object)- l'argomento della funzione copia il riferimento
objepersonpuntano allo stesso oggetto
// schema della memoria
let person = { name: 'Original' }; // heap @0x001
changeObject(person); // obj -> @0x001
obj.name = 'Changed'; // muta @0x001
console.log(person.name); // legge da @0x001
Domanda 3: riassegnazione vs mutazione della proprietΓ β
function test1(obj) {
obj.name = 'Modified'; // muta la proprietΓ
}
function test2(obj) {
obj = { name: 'New Object' }; // riassegna il parametro locale
}
let person = { name: 'Original' };
test1(person);
console.log('A:', person.name);
test2(person);
console.log('B:', person.name);
Clicca per vedere la risposta
// A: Modified
// B: Modified (non 'New Object')
Spiegazione:
test1: mutazione della proprietΓ
function test1(obj) {
obj.name = 'Modified'; // muta l'oggetto originale
}
test2: riassegnazione
function test2(obj) {
obj = { name: 'New Object' }; // cambia solo il binding locale
}
// person punta ancora all'oggetto originale
Schema della memoria:
// prima di test1
person ---> { name: 'Original' }
obj ---> { name: 'Original' } (stesso)
// dopo test1
person ---> { name: 'Modified' }
obj ---> { name: 'Modified' } (stesso)
// dentro test2
person ---> { name: 'Modified' } (invariato)
obj ---> { name: 'New Object' } (nuovo oggetto)
// dopo test2
person ---> { name: 'Modified' }
// obj locale Γ¨ scomparso
Domanda 4: passaggio di arrayβ
function modifyArray(arr) {
arr.push(4);
console.log('1:', arr);
}
function reassignArray(arr) {
arr = [5, 6, 7];
console.log('2:', arr);
}
let numbers = [1, 2, 3];
modifyArray(numbers);
console.log('3:', numbers);
reassignArray(numbers);
console.log('4:', numbers);
Clicca per vedere la risposta
// 1: [1, 2, 3, 4]
// 3: [1, 2, 3, 4]
// 2: [5, 6, 7]
// 4: [1, 2, 3, 4]
Spiegazione:
modifyArray: muta l'array originalereassignArray: riassegna solo il parametro locale
Domanda 5: confronto di uguaglianzaβ
// primitivi
let a = 10;
let b = 10;
console.log('A:', a === b);
// riferimenti
let obj1 = { value: 10 };
let obj2 = { value: 10 };
let obj3 = obj1;
console.log('B:', obj1 === obj2);
console.log('C:', obj1 === obj3);
Clicca per vedere la risposta
// A: true
// B: false
// C: true
Spiegazione:
I primitivi confrontano per valore; gli oggetti confrontano per riferimento.
obj1 === obj2; // false (riferimenti diversi)
obj1 === obj3; // true (stesso riferimento)
4. Copia superficiale vs copia profonda (Shallow Copy vs Deep Copy)β
Copia superficiale vs copia profonda
Copia superficiale (Shallow Copy)β
Definizione: viene copiato solo il livello superiore; gli oggetti annidati rimangono riferimenti condivisi.
Metodo 1: operatore spreadβ
const original = {
name: 'John',
address: { city: 'Taipei' },
};
const copy = { ...original };
copy.name = 'Jane';
console.log(original.name); // 'John'
copy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Kaohsiung' (influenzato)
Metodo 2: Object.assign()β
const original = { name: 'John', age: 30 };
const copy = Object.assign({}, original);
copy.name = 'Jane';
console.log(original.name); // 'John'
Metodo 3: copia superficiale di arrayβ
const arr1 = [1, 2, 3];
const arr2 = [...arr1];
const arr3 = arr1.slice();
const arr4 = Array.from(arr1);
arr2[0] = 999;
console.log(arr1[0]); // 1
Copia profonda (Deep Copy)β
Definizione: tutti i livelli vengono copiati ricorsivamente.
Metodo 1: JSON.parse(JSON.stringify(...))β
const original = {
name: 'John',
address: { city: 'Taipei' },
hobbies: ['reading', 'gaming'],
};
const copy = JSON.parse(JSON.stringify(original));
copy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Taipei'
copy.hobbies.push('coding');
console.log(original.hobbies); // ['reading', 'gaming']
Limitazioni:
const obj = {
date: new Date(), // -> stringa
func: () => {}, // ignorata
undef: undefined, // ignorato
symbol: Symbol('test'), // ignorato
regexp: /abc/, // -> {}
circular: null, // riferimento circolare lancia errore
};
obj.circular = obj;
JSON.parse(JSON.stringify(obj)); // errore o perdita di dati
Metodo 2: structuredClone()β
const original = {
name: 'John',
address: { city: 'Taipei' },
date: new Date(),
};
const copy = structuredClone(original);
console.log(copy.date instanceof Date); // true
Pro:
- Supporta Date, RegExp, Map, Set, ecc.
- Supporta riferimenti circolari
- Solitamente migliori prestazioni rispetto al deep clone manuale
Limitazioni:
- Non clona le funzioni
- Non clona i valori Symbol in tutti i pattern di utilizzo
Metodo 3: deep clone ricorsivoβ
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => deepClone(item));
}
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
const cloned = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
const original = {
name: 'John',
address: { city: 'Taipei' },
hobbies: ['reading'],
date: new Date(),
};
const copy = deepClone(original);
copy.address.city = 'Kaohsiung';
console.log(original.address.city); // 'Taipei'
Metodo 4: Lodashβ
import _ from 'lodash';
const original = {
name: 'John',
address: { city: 'Taipei' },
};
const copy = _.cloneDeep(original);
Confronto tra copia superficiale e profondaβ
| Caratteristica | Copia superficiale | Copia profonda |
|---|---|---|
| ProfonditΓ di copia | Solo livello superiore | Tutti i livelli |
| Oggetti annidati | Riferimenti condivisi | Completamente indipendenti |
| Prestazioni | PiΓΉ veloce | PiΓΉ lenta |
| Uso di memoria | Inferiore | Superiore |
| Caso d'uso | Strutture semplici | Strutture annidate complesse |
5. Insidie comuni (Common Pitfalls)β
Insidie comuni
Insidia 1: aspettarsi che argomenti primitivi mutino il valore esternoβ
function increment(num) {
num = num + 1;
return num;
}
let count = 5;
increment(count);
console.log(count); // 5
count = increment(count);
console.log(count); // 6
Insidia 2: aspettarsi che la riassegnazione sostituisca l'oggetto esternoβ
function resetObject(obj) {
obj = { name: 'Reset' }; // solo riassegnazione locale
}
let person = { name: 'Original' };
resetObject(person);
console.log(person.name); // 'Original'
// approccio corretto 1: muta la proprietΓ
function resetObject1(obj) {
obj.name = 'Reset';
}
// approccio corretto 2: restituisci un nuovo oggetto
function resetObject2(obj) {
return { name: 'Reset' };
}
person = resetObject2(person);
Insidia 3: assumere che lo spread sia una copia profondaβ
const original = {
user: { name: 'John' },
};
const copy = { ...original }; // superficiale
copy.user.name = 'Jane';
console.log(original.user.name); // 'Jane'
const deep = structuredClone(original);
Insidia 4: fraintendere constβ
const obj = { name: 'John' };
// obj = { name: 'Jane' }; // TypeError
obj.name = 'Jane'; // consentito
obj.age = 30; // consentito
const immutableObj = Object.freeze({ name: 'John' });
immutableObj.name = 'Jane';
console.log(immutableObj.name); // 'John'
Insidia 5: riferimento condiviso nei cicliβ
const arr = [];
const obj = { value: 0 };
for (let i = 0; i < 3; i++) {
obj.value = i;
arr.push(obj); // stesso riferimento oggetto ogni volta
}
console.log(arr);
// [{ value: 2 }, { value: 2 }, { value: 2 }]
const arr2 = [];
for (let i = 0; i < 3; i++) {
arr2.push({ value: i }); // nuovo oggetto ad ogni iterazione
}
console.log(arr2);
// [{ value: 0 }, { value: 1 }, { value: 2 }]
6. Best practiceβ
Best practice
β Raccomandatoβ
// 1. scegli una strategia di copia esplicita
const original = { name: 'John', age: 30 };
const copy1 = { ...original }; // superficiale
const copy2 = structuredClone(original); // profonda
// 2. evita effetti collaterali di mutazione nelle funzioni
function addItem(arr, item) {
return [...arr, item]; // stile immutabile
}
// 3. usa const per prevenire riassegnazioni accidentali
const config = { theme: 'dark' };
// 4. usa Object.freeze per costanti immutabili
const constants = Object.freeze({
PI: 3.14159,
MAX_SIZE: 100,
});
β Da evitareβ
function increment(num) {
num++; // inefficace per il primitivo esterno
}
const copy = { ...nested }; // non Γ¨ una copia profonda
for (let i = 0; i < 3; i++) {
arr.push(obj); // stesso riferimento oggetto riutilizzato
}
7. Riepilogo per i colloqui (Interview Summary)β
Riepilogo per i colloqui
Promemoria rapidoβ
Primitivi:
- String, Number, Boolean, Undefined, Null, Symbol, BigInt
- Passati per valore
- Immutabili
Riferimenti:
- Object, Array, Function, Date, RegExp, ecc.
- La variabile memorizza il riferimento all'oggetto nello heap
- Mutabili
Risposta esempio per un colloquioβ
D: JavaScript Γ¨ call-by-value o call-by-reference?
JavaScript Γ¨ call-by-value per tutti gli argomenti. Per gli oggetti, il valore copiato Γ¨ il riferimento (indirizzo di memoria).
- Argomenti primitivi: la copia del valore non influenza la variabile esterna.
- Argomenti oggetto: la copia del riferimento consente la mutazione dello stesso oggetto.
- Riassegnare il parametro locale non cambia il binding esterno.