Comme nous le savons, les objets peuvent stocker des propriétés.
Jusqu’à présent, une propriété était pour nous une simple paire « clé-valeur ». Mais une propriété d’objet est en réalité une chose plus flexible et plus puissante.
Dans ce chapitre, nous étudierons des options de configuration supplémentaires, et dans le prochain, nous verrons comment les transformer de manière invisible en fonctions getter/setter.
Les propriétés des objets, outre une value
, ont trois attributs spéciaux (appelés « drapeaux » ):
writable
– si true
, la valeur peut être modifiée, sinon elle est en lecture seule.
enumerable
– si true
, alors répertorié dans des boucles, sinon non répertorié.
configurable
– si true
, la propriété peut être supprimée et ces attributs peuvent être modifiés, sinon non.
Nous ne les avons pas encore vus, car généralement ils ne se présentent pas. Lorsque nous créons une propriété « de la manière habituelle », elles sont toutes true
. Mais nous pouvons aussi les modifier à tout moment.
Voyons d’abord comment obtenir ces drapeaux.
La méthode Object.getOwnPropertyDescriptor permet d'interroger toutes les informations sur une propriété.
La syntaxe est :
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
obj
Objet à partir duquel obtenir des informations.
propertyName
Le nom de la propriété.
La valeur renvoyée est un objet dit « descripteur de propriété » : il contient la valeur et tous les drapeaux.
Par exemple:
laissez l'utilisateur = { prénom : "Jean" } ; let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); /* descripteur de propriété : { "value": "Jean", "inscriptible": vrai, "énumérable": vrai, "configurable": vrai } */
Pour changer les drapeaux, nous pouvons utiliser Object.defineProperty.
La syntaxe est :
Object.defineProperty (obj, propertyName, descripteur)
obj
, propertyName
L'objet et sa propriété pour appliquer le descripteur.
descriptor
Objet descripteur de propriété à appliquer.
Si la propriété existe, defineProperty
met à jour ses indicateurs. Sinon, il crée la propriété avec la valeur et les indicateurs donnés ; dans ce cas, si un indicateur n'est pas fourni, il est supposé false
.
Par exemple, ici, un name
de propriété est créé avec tous les indicateurs faux :
laissez l'utilisateur = {} ; Object.defineProperty(utilisateur, "nom", { valeur : "Jean" }); let descriptor = Object.getOwnPropertyDescriptor(user, 'name'); alert( JSON.stringify(descriptor, null, 2 ) ); /* { "value": "Jean", "inscriptible": faux, "énumérable": faux, "configurable": faux } */
Comparez-le avec user.name
« normalement créé » ci-dessus : désormais, tous les indicateurs sont faux. Si ce n'est pas ce que nous voulons, nous ferions mieux de les définir sur true
dans descriptor
.
Voyons maintenant les effets des drapeaux par exemple.
Rendons user.name
non inscriptible (ne peut pas être réaffecté) en modifiant l'indicateur writable
:
laissez l'utilisateur = { prénom : "Jean" } ; Object.defineProperty(utilisateur, "nom", { inscriptible : faux }); nom d'utilisateur = "Pete" ; // Erreur : Impossible d'attribuer la propriété 'nom' en lecture seule
Désormais, personne ne peut changer le nom de notre utilisateur, à moins qu'il n'applique sa propre defineProperty
pour remplacer la nôtre.
Les erreurs n'apparaissent qu'en mode strict
En mode non strict, aucune erreur ne se produit lors de l'écriture dans des propriétés non inscriptibles et autres. Mais l’opération n’aboutira toujours pas. Les actions violant un drapeau sont simplement ignorées en silence dans les cas non stricts.
Voici le même exemple, mais la propriété est créée de toutes pièces :
laissez l'utilisateur = { } ; Object.defineProperty(utilisateur, "nom", { valeur : "Jean", // pour les nouvelles propriétés, nous devons explicitement lister ce qui est vrai énumérable : vrai, configurable : vrai }); alert(utilisateur.nom); // John nom d'utilisateur = "Pete" ; // Erreur
Ajoutons maintenant un toString
personnalisé à user
.
Normalement, un toString
intégré pour les objets n'est pas énumérable, il n'apparaît pas dans for..in
. Mais si nous ajoutons notre propre toString
, alors par défaut, il apparaît dans for..in
, comme ceci :
laissez l'utilisateur = { nom : "Jean", versChaîne() { renvoie this.name ; } } ; // Par défaut, nos deux propriétés sont listées : pour (laisser la clé dans l'utilisateur) alert(key); // nom, versChaîne
Si cela ne nous plaît pas, nous pouvons définir enumerable:false
. Ensuite, il n'apparaîtra pas dans une boucle for..in
, tout comme celle intégrée :
laissez l'utilisateur = { nom : "Jean", versChaîne() { renvoie this.name ; } } ; Object.defineProperty(utilisateur, "toString", { énumérable : faux }); // Maintenant notre toString disparaît : pour (laisser la clé dans l'utilisateur) alert(key); // nom
Les propriétés non énumérables sont également exclues de Object.keys
:
alerte(Object.keys(utilisateur)); // nom
L'indicateur non configurable ( configurable:false
) est parfois prédéfini pour les objets et propriétés intégrés.
Une propriété non configurable ne peut pas être supprimée, ses attributs ne peuvent pas être modifiés.
Par exemple, Math.PI
n'est pas inscriptible, non énumérable et non configurable :
let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI'); alert( JSON.stringify(descriptor, null, 2 ) ); /* { "valeur": 3.141592653589793, "inscriptible": faux, "énumérable": faux, "configurable": faux } */
Ainsi, un programmeur n'est pas en mesure de modifier la valeur de Math.PI
ou de l'écraser.
Math.PI = 3 ; // Erreur, car il est accessible en écriture : false // supprimer Math.PI ne fonctionnera pas non plus
Nous ne pouvons pas non plus modifier Math.PI
pour qu'il soit à nouveau writable
:
// Erreur, car configurable : false Object.defineProperty(Math, "PI", { inscriptible : true });
Nous ne pouvons absolument rien faire avec Math.PI
.
Rendre une propriété non configurable est une voie à sens unique. Nous ne pouvons pas le modifier avec defineProperty
.
Attention : configurable: false
empêche les changements de flags de propriété et sa suppression, tout en permettant de changer sa valeur.
Ici, user.name
n'est pas configurable, mais nous pouvons toujours le modifier (car il est accessible en écriture) :
laissez l'utilisateur = { prénom : "Jean" } ; Object.defineProperty(utilisateur, "nom", { configurable : faux }); nom d'utilisateur = "Pete" ; // fonctionne bien supprimer user.name ; // Erreur
Et ici, nous faisons user.name
une constante « scellée pour toujours », tout comme le Math.PI
intégré :
laissez l'utilisateur = { prénom : "Jean" } ; Object.defineProperty(utilisateur, "nom", { inscriptible : faux, configurable : faux }); // ne pourra pas changer user.name ou ses drapeaux // tout ça ne marchera pas : nom d'utilisateur = "Pete" ; supprimer user.name ; Object.defineProperty(user, "name", { value: "Pete" });
Le seul changement d'attribut possible : writable true → false
Il existe une exception mineure concernant le changement de drapeaux.
On peut changer writable: true
en false
pour une propriété non configurable, empêchant ainsi sa modification de valeur (pour ajouter une autre couche de protection). Mais pas l’inverse.
Il existe une méthode Object.defineProperties(obj, descriptors) qui permet de définir plusieurs propriétés à la fois.
La syntaxe est :
Objet.defineProperties(obj, { prop1 : descripteur1, prop2 : descripteur2 //... });
Par exemple:
Object.defineProperties (utilisateur, { nom : { valeur : "John", inscriptible : false }, nom : { valeur : "Smith", inscriptible : false }, //... });
Nous pouvons donc définir plusieurs propriétés à la fois.
Pour obtenir tous les descripteurs de propriétés à la fois, nous pouvons utiliser la méthode Object.getOwnPropertyDescriptors(obj).
Avec Object.defineProperties
il peut être utilisé comme moyen « prenant en compte les drapeaux » pour cloner un objet :
laissez clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
Normalement, lorsque nous clonons un objet, nous utilisons une affectation pour copier les propriétés, comme ceci :
pour (laisser la clé dans l'utilisateur) { clone[clé] = utilisateur[clé] }
…Mais cela ne copie pas les drapeaux. Donc, si nous voulons un « meilleur » clone, alors Object.defineProperties
est préféré.
Une autre différence est que for..in
ignore les propriétés symboliques et non énumérables, mais Object.getOwnPropertyDescriptors
renvoie tous les descripteurs de propriété, y compris ceux symboliques et non énumérables.
Les descripteurs de propriétés fonctionnent au niveau des propriétés individuelles.
Il existe également des méthodes qui limitent l'accès à l' ensemble de l'objet :
Objet.preventExtensions(obj)
Interdit l'ajout de nouvelles propriétés à l'objet.
Objet.seal(obj)
Interdit l'ajout/suppression de propriétés. Ensembles configurable: false
pour toutes les propriétés existantes.
Objet.freeze(obj)
Interdit l'ajout/suppression/modification de propriétés. Définit configurable: false, writable: false
pour toutes les propriétés existantes.
Et il y a aussi des tests pour eux :
Objet.isExtensible(obj)
Renvoie false
si l'ajout de propriétés est interdit, sinon true
.
Objet.isSealed(obj)
Renvoie true
si l'ajout/suppression de propriétés est interdit et que toutes les propriétés existantes sont configurable: false
.
Objet.isFrozen(obj)
Renvoie true
si l'ajout/suppression/modification de propriétés est interdit et que toutes les propriétés actuelles sont configurable: false, writable: false
.
Ces méthodes sont rarement utilisées en pratique.