L'immutabilità è un principio fondamentale nella programmazione funzionale, e ha molto da offrire anche ai programmi orientati agli oggetti. Un oggetto mutabile è un oggetto il cui stato può essere modificato dopo la sua creazione. Un oggetto immutabile è un oggetto il cui stato non può essere modificato dopo la sua creazione.
Qual è un esempio di oggetto immutabile in JavaScript?
In JavaScript, alcuni tipi incorporati (numeri, stringhe) sono immutabili, ma gli oggetti personalizzati sono generalmente mutabili.
Alcuni oggetti JavaScript immutabili incorporati sono 'Math', 'Date'.
Ecco alcuni modi per aggiungere/simulare l'immutabilità su semplici oggetti JavaScript.
Proprietà costanti dell'oggetto
Combinando 'writable: false' e 'configurable: false', puoi essenzialmente creare una costante (non modificabile, ridefinibile o eliminabile) come proprietà di un oggetto, come:
let myObject = {};
Object.defineProperty(myObject, 'number', {
value: 42,
writable: false,
configurable: false,
});
console.log(myObject.number); // 42
myObject.number = 43;
console.log(myObject.number); // 42
Impedire le estensioni
Se vuoi impedire che a un oggetto vengano aggiunte nuove proprietà, ma lasciare intatte le altre proprietà dell'oggetto, chiama 'Object.preventExtensions(...)':
var myObject = {
a: 2,
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
In modalità non rigorosa, la creazione di 'b' fallisce silenziosamente. In modalità rigorosa, genera un 'TypeError'.
Seal
'Object.seal()' crea un oggetto "sigillato", il che significa che prende un oggetto esistente e sostanzialmente chiama 'Object.preventExtensions()' su di esso, ma contrassegna anche tutte le sue proprietà esistenti come 'configurable: false'.
Quindi, non solo non puoi aggiungere altre proprietà, ma non puoi nemmeno riconfigurare o eliminare alcuna proprietà esistente (anche se puoi ancora modificarne i valori).
Freeze
'Object.freeze()' crea un oggetto congelato, il che significa che prende un oggetto esistente e sostanzialmente chiama 'Object.seal()' su di esso, ma contrassegna anche tutte le proprietà "data accessor" come 'writable: false', in modo che i loro valori non possano essere modificati.
Questo approccio è il livello più alto di immutabilità che puoi ottenere per un oggetto stesso, poiché impedisce qualsiasi modifica all'oggetto o a qualsiasi delle sue proprietà dirette (anche se, come menzionato sopra, il contenuto di qualsiasi altro oggetto a cui si fa riferimento non viene influenzato).
var immutable = Object.freeze({});
Congelare un oggetto non consente di aggiungere nuove proprietà a un oggetto e impedisce di rimuovere o alterare le proprietà esistenti. 'Object.freeze()' preserva l'enumerabilità, la configurabilità, la scrivibilità e il prototipo dell'oggetto. Restituisce l'oggetto passato e non crea una copia congelata.
Quali sono i pro e i contro dell'immutabilità?
Pro
- Rilevamento delle modifiche più semplice - L'uguaglianza degli oggetti può essere determinata in modo performante e semplice tramite l'uguaglianza referenziale. Questo è utile per confrontare le differenze tra gli oggetti in React e Redux.
- I programmi con oggetti immutabili sono meno complicati da pensare, poiché non devi preoccuparti di come un oggetto può evolvere nel tempo.
- Le copie difensive non sono più necessarie quando gli oggetti immutabili vengono restituiti da o passati a funzioni, poiché non c'è possibilità che un oggetto immutabile venga modificato da essa.
- Facile condivisione tramite riferimenti - Una copia di un oggetto è buona quanto un'altra, quindi puoi memorizzare nella cache gli oggetti o riutilizzare lo stesso oggetto più volte.
- Thread-safe - Gli oggetti immutabili possono essere usati in sicurezza tra i thread in un ambiente multi-thread poiché non c'è il rischio che vengano modificati in altri thread in esecuzione contemporaneamente.
- Utilizzando librerie come ImmutableJS, gli oggetti vengono modificati usando la condivisione strutturale e è necessaria meno memoria per avere più oggetti con strutture simili.
Contro
- Implementazioni ingenue di strutture dati immutabili e delle sue operazioni possono comportare prestazioni estremamente scarse perché vengono creati nuovi oggetti ogni volta. Si consiglia di utilizzare librerie per strutture dati immutabili efficienti e operazioni che sfruttano la condivisione strutturale.
- L'allocazione (e la deallocazione) di molti piccoli oggetti piuttosto che la modifica di quelli esistenti può causare un impatto sulle prestazioni. La complessità dell'allocatore o del garbage collector di solito dipende dal numero di oggetti nell'heap.
- Le strutture dati cicliche come i grafi sono difficili da costruire. Se hai due oggetti che non possono essere modificati dopo l'inizializzazione, come puoi farli puntare l'uno all'altro?
Come puoi ottenere l'immutabilità nel tuo codice?
L'alternativa è usare dichiarazioni 'const' combinate con le tecniche menzionate sopra per la creazione. Per gli oggetti "mutanti", usa l'operatore 'spread', 'Object.assign', 'Array.concat()', ecc., per creare nuovi oggetti invece di mutare l'oggetto originale.
Esempi:
// Esempio di array
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]
// Esempio di oggetto
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: "human", name: "John"}
const alienJohn = { ...john, race: 'alien' }; // {race: "alien", name: "John"}