Il existe deux types de propriétés d'objet.
Le premier type est celui des propriétés des données . Nous savons déjà comment travailler avec eux. Toutes les propriétés que nous avons utilisées jusqu'à présent étaient des propriétés de données.
Le deuxième type de propriété est quelque chose de nouveau. C'est une propriété d'accesseur . Ce sont essentiellement des fonctions qui s'exécutent lors de l'obtention et de la définition d'une valeur, mais qui ressemblent à des propriétés normales pour un code externe.
Les propriétés des accesseurs sont représentées par les méthodes « getter » et « setter ». Dans un objet littéral, ils sont désignés par get
et set
:
soit obj = { obtenir propName() { // getter, le code exécuté lors de l'obtention de obj.propName }, définir propName (valeur) { // setter, le code exécuté lors de la définition de obj.propName = value } } ;
Le getter fonctionne lorsque obj.propName
est lu, le setter – lorsqu'il est attribué.
Par exemple, nous avons un objet user
avec name
et surname
:
laissez l'utilisateur = { nom : "Jean", nom de famille : « Smith » } ;
Nous voulons maintenant ajouter une propriété fullName
, qui devrait être "John Smith"
. Bien sûr, nous ne voulons pas copier-coller les informations existantes, nous pouvons donc les implémenter en tant qu'accesseur :
laissez l'utilisateur = { nom : "Jean", nom de famille : « Smith », obtenir fullName() { return `${this.name} ${this.surname}` ; } } ; alerte (utilisateur.fullName); // John Smith
De l’extérieur, une propriété accesseur ressemble à une propriété ordinaire. C'est l'idée des propriétés des accesseurs. Nous n'appelons pas user.fullName
en tant que fonction, nous le lisons normalement : le getter s'exécute en coulisses.
Pour l'instant, fullName
n'a qu'un getter. Si nous essayons d'attribuer user.fullName=
, il y aura une erreur :
laissez l'utilisateur = { obtenir fullName() { retourner `...` ; } } ; utilisateur.fullName = "Test" ; // Erreur (la propriété n'a qu'un getter)
Corrigeons-le en ajoutant un setter pour user.fullName
:
laissez l'utilisateur = { nom : "Jean", nom de famille : « Smith », obtenir fullName() { return `${this.name} ${this.surname}` ; }, définir le nom complet (valeur) { [this.name, this.surname] = value.split(" "); } } ; // set fullName est exécuté avec la valeur donnée. utilisateur.fullName = "Alice Cooper" ; alert(utilisateur.nom); //Alice alert(utilisateur.nom de famille); // Tonnelier
En conséquence, nous avons une propriété « virtuelle » fullName
. Il est lisible et inscriptible.
Les descripteurs des propriétés des accesseurs sont différents de ceux des propriétés des données.
Pour les propriétés d'accesseur, il n'y a pas value
ou writable
, mais à la place il y a des fonctions get
et set
.
Autrement dit, un descripteur d'accesseur peut avoir :
get
– une fonction sans arguments, qui fonctionne lorsqu'une propriété est lue,
set
– une fonction avec un argument, appelée lorsque la propriété est définie,
enumerable
– comme pour les propriétés des données,
configurable
– comme pour les propriétés des données.
Par exemple, pour créer un accesseur fullName
avec defineProperty
, on peut passer un descripteur avec get
et set
:
laissez l'utilisateur = { nom : "Jean", nom de famille : « Smith » } ; Object.defineProperty(utilisateur, 'fullName', { obtenir() { return `${this.name} ${this.surname}` ; }, définir (valeur) { [this.name, this.surname] = value.split(" "); } }); alerte (utilisateur.fullName); // John Smith for(laisser la clé dans l'utilisateur) alert(key); // nom, prénom
Veuillez noter qu'une propriété peut être soit un accesseur (possédant des méthodes get/set
), soit une propriété data (ayant une value
), pas les deux.
Si nous essayons de fournir à la fois get
et value
dans le même descripteur, il y aura une erreur :
// Erreur : descripteur de propriété non valide. Objet.defineProperty({}, 'prop', { obtenir() { retour 1 }, valeur : 2 });
Les getters/setters peuvent être utilisés comme wrappers sur les valeurs de propriétés « réelles » pour obtenir plus de contrôle sur les opérations avec elles.
Par exemple, si nous voulons interdire les noms trop courts pour user
, nous pouvons avoir un name
de définition et conserver la valeur dans une propriété distincte _name
:
laissez l'utilisateur = { obtenir le nom() { renvoie this._name ; }, définir le nom (valeur) { si (valeur.longueur < 4) { alert("Le nom est trop court, nécessite au moins 4 caractères"); retour; } this._name = valeur ; } } ; nom d'utilisateur = "Pete" ; alert(utilisateur.nom); // Pierre nom d'utilisateur = "" ; // Le nom est trop court...
Ainsi, le nom est stocké dans la propriété _name
et l'accès se fait via getter et setter.
Techniquement, le code externe est capable d'accéder au nom directement en utilisant user._name
. Mais il existe une convention largement connue selon laquelle les propriétés commençant par un trait de soulignement "_"
sont internes et ne doivent pas être modifiées depuis l'extérieur de l'objet.
L'une des grandes utilisations des accesseurs est qu'ils permettent de prendre le contrôle d'une propriété de données « normale » à tout moment en la remplaçant par un getter et un setter et d'ajuster son comportement.
Imaginez que nous commencions à implémenter des objets utilisateur en utilisant les propriétés de données name
et age
:
function Utilisateur (nom, âge) { this.name = nom ; this.age = âge; } let john = new User("John", 25); alerte( john.age ); // 25
…Mais tôt ou tard, les choses pourraient changer. Au lieu de age
nous pouvons décider de stocker birthday
, car c'est plus précis et plus pratique :
function Utilisateur (nom, anniversaire) { this.name = nom ; this.birthday = anniversaire ; } let john = new User("John", new Date(1992, 6, 1));
Maintenant, que faire de l'ancien code qui utilise toujours la propriété age
?
Nous pouvons essayer de trouver tous ces endroits et de les réparer, mais cela prend du temps et peut être difficile à faire si ce code est utilisé par de nombreuses autres personnes. Et en plus, age
est une bonne chose à avoir chez user
, non ?
Gardons-le.
L'ajout d'un getter pour age
résout le problème :
function Utilisateur (nom, anniversaire) { this.name = nom ; this.birthday = anniversaire ; // l'âge est calculé à partir de la date et de l'anniversaire actuels Object.defineProperty(this, "âge", { obtenir() { let TodayYear = new Date().getFullYear(); revenir aujourd'huiAnnée - this.birthday.getFullYear(); } }); } let john = new User("John", new Date(1992, 6, 1)); alerte( john.anniversaire ); // l'anniversaire est disponible alerte( john.age ); // ...ainsi que l'âge
Maintenant, l'ancien code fonctionne aussi et nous avons une belle propriété supplémentaire.