Descrittori di proprietà JavaScript
Scopri come funzionano i flag di proprietà JavaScript (writable, enumerable, configurable) e i descrittori, inclusi descrittori dati e accessor, defineProperty, getOwnPropertyDescriptors, freeze, seal e il comportamento in strict mode.
I flag e i descrittori di proprietà JavaScript offrono un controllo preciso sulle proprietà degli object, consentendo uno sviluppo di applicazioni robusto e sicuro. Questo articolo esplora queste funzionalità nel dettaglio, fornendo approfondimenti pratici ed esempi di codice per aiutarti a gestire efficacemente il comportamento delle proprietà.
Comprendere gli attributi di proprietà in JavaScript
Gli object JavaScript sono collezioni di proprietà, e ciascuna proprietà ha degli attributi associati che ne definiscono il comportamento. Questi attributi, spesso chiamati flag di proprietà, includono:
- Writable: Determina se il valore della proprietà può essere modificato.
- Enumerable: Controlla se la proprietà è visibile durante l'enumerazione, ad esempio in un ciclo
for...in. - Configurable: Specifica se la proprietà può essere eliminata o modificata.
Questi flag sono fondamentali per controllare l'accesso alle proprietà degli object, garantire l'integrità dei dati e implementare l'incapsulamento nelle applicazioni JavaScript.
Approfondimento sui descrittori di proprietà
I descrittori di proprietà forniscono informazioni dettagliate su una proprietà di un object, racchiudendo il suo valore e i suoi flag. Vengono recuperati tramite Object.getOwnPropertyDescriptor(obj, propName) e impostati tramite Object.defineProperty(obj, propName, descriptor). Un object descrittore di proprietà può contenere:
value: Il valore associato alla proprietà.writable: Indica se il valore della proprietà può essere modificato.enumerable: Indica se la proprietà è enumerabile.configurable: Determina se il descrittore della proprietà può essere modificato e se la proprietà può essere eliminata dall'object.
Nota: Quando si crea una proprietà nel modo normale (user.name = "John"), tutti e tre i flag vengono impostati a true. Ma quando si definisce una nuova proprietà tramite Object.defineProperty, qualsiasi flag non specificato ha come valore predefinito false.
Object.getOwnPropertyDescriptor esamina solo le proprietà proprie dell'object. Se si richiede una proprietà che l'object eredita dal suo prototipo (o una proprietà che non esiste affatto), restituisce undefined.
Per saperne di più su come gli object ereditano le proprietà, consulta Ereditarietà prototipale.
Descrittori dati vs. descrittori accessor
Finora abbiamo descritto i descrittori dati, che memorizzano un value insieme al flag writable. JavaScript supporta anche i descrittori accessor, che sostituiscono value/writable con funzioni getter e setter:
get: una funzione chiamata quando la proprietà viene letta (non accetta argomenti).set: una funzione chiamata quando viene assegnato un valore alla proprietà (riceve il nuovo valore).
Un descrittore è o un descrittore dati o un descrittore accessor — mai entrambi. Combinare value/writable con get/set genera un errore. Entrambi i tipi condividono comunque i flag enumerable e configurable.
Le proprietà accessor sono il modo in cui si calcola un valore durante la lettura o lo si valida durante la scrittura. Qui esponiamo un accessor fullName supportato da due proprietà dati:
Per un trattamento più completo della sintassi get/set (inclusa la forma abbreviata all'interno dei letterali object), consulta Getter e setter di proprietà. Poiché getter e setter vengono eseguiti con this legato all'object, è utile anche comprendere Metodi degli object e "this".
Definire e leggere più proprietà in una volta
Per lavorare con più proprietà in un unico passaggio, JavaScript fornisce le controparti plurali dei metodi visti sopra:
Object.defineProperties(obj, descriptors)definisce più proprietà da una mappa di descrittori.Object.getOwnPropertyDescriptors(obj)restituisce i descrittori di tutte le proprietà proprie (incluse quelle non enumerabili e le chiavi symbol) come un unico object.
Object.getOwnPropertyDescriptors è particolarmente utile per clonare un object con i suoi flag — uno spread semplice o Object.assign copia i valori ma reimposta ogni flag a true e salta gli accessor.
Manipolazione dei flag di proprietà
Comprendere e manipolare i flag di proprietà è fondamentale per uno sviluppo JavaScript efficace. Esploriamo come controllare questi flag per affinare il comportamento delle proprietà.
Rendere una proprietà non modificabile
Impedire le modifiche a una proprietà garantisce la coerenza dei dati. Questo si ottiene impostando il flag writable a false.
Il comportamento dell'assegnazione fallita dipende dalla modalità in cui viene eseguito il codice. In modalità non-strict, la scrittura su una proprietà non modificabile fallisce silenziosamente: l'assegnazione viene semplicemente ignorata, non viene generato alcun errore e l'esecuzione continua — il che può nascondere bug. In strict mode ("use strict", e come impostazione predefinita all'interno dei moduli ES e dei corpi delle classi), la stessa assegnazione genera un TypeError. La regola si applica a qualsiasi operazione che viola un flag: l'eliminazione di una proprietà non configurabile o l'aggiunta di una proprietà a un object non estensibile falliscono anch'esse silenziosamente in modalità non-strict e generano un'eccezione in strict mode.
Nascondere una proprietà dall'enumerazione
A volte è necessario nascondere le proprietà dai processi di enumerazione, come i cicli for...in. Questo si può fare impostando il flag enumerable a false.
Impedire l'eliminazione e la modifica di una proprietà
Per garantire che una proprietà rimanga una parte costante di un object, imposta il flag configurable a false.
Contrassegnare una proprietà come non configurabile è un'operazione a senso unico — non esiste alcun flag per renderla di nuovo configurabile, e non è più possibile attivare/disattivare enumerable né passare la proprietà da descrittore dati ad accessor.
Esistono tuttavia due eccezioni importanti quando una proprietà è non configurabile:
- È possibile cambiare
writabledatrueafalse(ma non viceversa, dafalseatrue). - Se la proprietà è ancora
writable: true, è possibile modificarne ilvalue— sia tramite assegnazione diretta che tramiteObject.defineProperty.
In altre parole, configurable: false blocca la forma della proprietà, non necessariamente il suo valore. Per congelare davvero il valore di una proprietà, imposta sia configurable: false che writable: false.
API di livello superiore basate su questi flag
Raramente è necessario impostare i flag una proprietà alla volta. JavaScript include tre metodi integrati che modificano questi flag sull'intero object:
Object.preventExtensions(obj)— impedisce l'aggiunta di nuove proprietà. Le proprietà esistenti possono ancora essere modificate o eliminate.Object.seal(obj)— impedisce l'aggiunta e l'eliminazione di proprietà contrassegnando ogni proprietà esistente conconfigurable: false. I valori possono ancora cambiare.Object.freeze(obj)— il più restrittivo: sigilla l'object e rende ogni proprietàwritable: false, così nulla può essere aggiunto, rimosso o modificato.
Ogni metodo ha un controllo corrispondente: Object.isExtensible, Object.isSealed e Object.isFrozen. Nota che questi operano a un solo livello di profondità — Object.freeze non congela gli object annidati (è un "freeze superficiale").
Conclusione
I flag e i descrittori di proprietà ti offrono un controllo preciso sul comportamento delle proprietà degli object:
- Un descrittore dati abbina un
valueawritable; un descrittore accessor usa invece le funzioniget/set. Entrambi condividonoenumerableeconfigurable. - Leggi i flag con
Object.getOwnPropertyDescriptor(una proprietà) oObject.getOwnPropertyDescriptors(tutte le proprietà proprie); scrivili conObject.definePropertyoObject.defineProperties. Le proprietà ereditate e quelle mancanti restituisconoundefined. configurable: falseè irreversibile e blocca la forma della proprietà, anche se una proprietà ancorawritablepuò avere il suo valore modificato e il suo flagwritabledisattivato.- La violazione di un flag fallisce silenziosamente in modalità non-strict, ma genera un
TypeErrorin strict mode. - Usa
Object.freeze,Object.sealeObject.preventExtensionsquando vuoi bloccare un intero object invece di singoli flag.
Prossimi passi: approfondisci Getter e setter di proprietà per la sintassi accessor, Metodi degli object e "this" per il comportamento di this al loro interno, e Ereditarietà prototipale per vedere come la ricerca delle proprietà percorre la catena dei prototipi.