Question 1Avec un getter qui renvoie 30 - this.reserved, qu'obtiens-tu si tu mets reserved à 20 puis lis le getter ?
Getters et setters — exécuter du code à la lecture ou à l'affectation
Exécuter du code à la lecture ou à l'affectation d'une propriété : propriétés calculées, validation, setter appelé dans le constructor et lecture seule.
Imagine la page d'inscription à un séminaire qui affiche le nombre de places restantes, calculé comme « capacité − inscriptions ». Si tu calcules les places restantes à la création de l'instance et que tu les stockes, l'affichage ne bouge plus quand d'autres personnes s'inscrivent. Et si quelqu'un affecte une capacité inférieure au nombre d'inscriptions, rien ne l'en empêche.
Cet article traite des getters et des setters, qui exécutent du code quand on lit ou qu'on affecte une propriété.
Recalculer à chaque lecture — get et les propriétés calculées
Si tu calcules les places restantes (« capacité − inscriptions ») dans le constructor et que tu les stockes, elles gardent la valeur du moment de la création, même quand le nombre d'inscriptions change ensuite. En faire une méthode permet de recalculer, mais ceux qui l'appellent doivent alors retenir qu'on n'ajoute pas de parenthèses pour la capacité, et qu'on en ajoute pour les places restantes.
Un getter (une méthode écrite dans une classe avec get devant, qui s'exécute quand la propriété est lue) s'écrit sans paramètre, comme get remainingSeats() { ... }, et lire la propriété donne la valeur renvoyée par son return.
Une propriété comme remainingSeats, calculée à partir d'autres propriétés puis renvoyée, s'appelle une propriété calculée.
class Seminar {
reserved = 0; // Nombre d'inscriptions
constructor(capacity) {
this.capacity = capacity; // Capacité
}
// À chaque lecture, calcule à partir de la capacité et des inscriptions actuelles
get remainingSeats() {
return this.capacity - this.reserved;
}
}
const seminar = new Seminar(30);
console.log(seminar.remainingSeats); // 30 (lu sans parenthèses)
// Relire après 12 inscriptions
seminar.reserved = 12;
console.log(seminar.remainingSeats); // 18
Le 30 de la ligne du haut a été calculé une seule fois, au moment du new, puis stocké. Le getter de la ligne du bas ne stocke rien et exécute l'expression du return à chaque lecture de seminar.remainingSeats : tu peux donc modifier reserved autant de fois que tu veux sans ajouter de ligne pour recalculer les places restantes.
Appeler un getter avec des parenthèses lève une TypeError
Un getter s'écrit comme une méthode, mais on l'utilise sans parenthèses. Si tu écris seminar.remainingSeats(), tu essaies d'appeler comme une fonction la valeur renvoyée, 18, ce qui lève TypeError: seminar.remainingSeats is not a function.
Vérifier les valeurs affectées — set et la validation
À l'approche du séminaire, il arrive qu'on puisse passer la capacité de la salle de 30 à 40. Tu pourrais écrire une vérification avec if et throw dans le constructor, mais elle ne s'exécute qu'au moment du new. Si tu affectes ensuite seminar.capacity = 5, une valeur inférieure aux 12 inscriptions, rien ne l'arrête, et les places restantes affichées tombent à -7.
Un setter (une méthode écrite dans une classe avec set devant, qui s'exécute quand on affecte la propriété) reçoit la valeur affectée dans son paramètre value. La valeur elle-même est stockée sous un autre nom, comme _capacity, et renvoyée par un getter qui porte le nom de la propriété, get capacity.
Le _ en tête de nom est une convention qui signifie « à ne pas toucher directement depuis l'extérieur ».
class Seminar {
constructor(capacity, reserved) {
this.reserved = reserved; // Nombre d'inscriptions
this._capacity = capacity; // La valeur de la capacité elle-même va dans _capacity
}
get capacity() { return this._capacity; }
set capacity(value) { // La valeur affectée arrive dans value
if (value < this.reserved) {
throw new Error(`La capacité ne peut pas être inférieure aux ${this.reserved} inscrits : ${value}`);
}
this._capacity = value;
}
}
const seminar = new Seminar(30, 12);
seminar.capacity = 40; // set capacity s'exécute et stocke 40
console.log(seminar.capacity); // 40 (get capacity s'exécute)
seminar.capacity = 5; // Error: La capacité ne peut pas être inférieure aux 12 inscrits : 5
=, c'est set qui s'exécute ; quand elle est lue ailleurs, c'est get. Une valeur qui échoue à la vérification n'atteint jamais _capacity.La ligne ③ lève une exception : si tu ne l'entoures pas d'un try, les lignes suivantes ne s'exécutent pas. Tant que tu affectes capacity, la valeur passe par le setter, mais si tu écris directement seminar._capacity = 5, elle échappe à la vérification. Le prochain article montre comment l'empêcher.
Affecter le même nom dans un setter lève une RangeError
Si tu écris this.capacity = value; dans set capacity(value), cette affectation rappelle le setter, qui affecte à nouveau, et ainsi de suite jusqu'à lever RangeError: Maximum call stack size exceeded. Stocke la valeur sous un autre nom, comme _capacity.
Passer par le setter dès la création — l'affectation dans le constructor
Dans le Seminar ci-dessus, le constructor stocke la valeur directement dans _capacity : new Seminar(5, 12) crée donc une instance sans aucune vérification. Si tu écris aussi le même if dans le constructor, il faut corriger deux endroits chaque fois que la condition change, et si tu en oublies un, new et l'affectation ne donnent plus le même résultat.
Si, dans le constructor, tu affectes le nom sans _, comme dans this.capacity = capacity, le setter s'exécute aussi pour cette ligne. La vérification reste à un seul endroit, le setter, et les valeurs passées à new comme celles affectées plus tard sont contrôlées avec la même condition.
class Seminar {
constructor(capacity, reserved) {
this.reserved = reserved; // Stocker d'abord les inscriptions utilisées par la vérification
this.capacity = capacity; // Le nom n'a pas de _, donc set capacity s'exécute
}
get capacity() { return this._capacity; }
set capacity(value) {
if (value < this.reserved) {
throw new Error(`La capacité ne peut pas être inférieure aux ${this.reserved} inscrits : ${value}`);
}
this._capacity = value;
}
}
try {
new Seminar(5, 12); // new passe aussi par la vérification du setter
} catch (error) {
console.log(error.message); // La capacité ne peut pas être inférieure aux 12 inscrits : 5
}
new Seminar(5, 12)— appelle le constructor- L'exception du setter remonte jusqu'ici, aucune instance n'est renvoyée
this.reserved = 12— stocke les inscriptions avant la vérificationthis.capacity = 5— le nom n'a pas de_, donc appelle set capacity
valuevaut 5 etthis.reservedvaut 125 < 12, doncthrow— rien n'entre dans_capacity
this.capacity = 5 appelle set capacity. L'exception sort du constructor et arrive jusqu'au try.Si tu places this.reserved = reserved sous this.capacity = capacity, this.reserved vaut undefined au moment où le setter s'exécute. Dans une comparaison numérique, undefined devient NaN, et 5 < NaN vaut false : l'instance est donc créée avec une capacité de 5.
Empêcher l'affectation — des propriétés avec un getter seulement
Les places restantes découlent de la capacité et des inscriptions. Si le code de la page peut écrire seminar.remainingSeats = 100, la page affiche 100 places restantes pour une capacité de 30 et 12 inscriptions, et les trois valeurs ne concordent plus.
Une propriété qui n'a qu'un getter, sans setter du même nom, est en lecture seule (on peut la lire, mais pas l'affecter). Dans le corps d'une classe, ou en mode strict (un mode d'exécution qui transforme certaines erreurs silencieuses en exceptions) comme dans l'environnement d'exécution de cette page, la ligne qui l'affecte lève une TypeError. Hors mode strict, l'affectation est ignorée sans rien signaler.
class Seminar {
constructor(capacity, reserved) {
this.capacity = capacity;
this.reserved = reserved;
}
get remainingSeats() { return this.capacity - this.reserved; } // Pas de setter
}
const seminar = new Seminar(30, 12);
try {
seminar.remainingSeats = 100; // Affectation d'un nom sans set
} catch (error) {
console.log(error.name); // TypeError
}
console.log(seminar.remainingSeats); // 18 (comme avant l'affectation)
// Pour changer les places restantes, modifie les inscriptions dont elles découlent
seminar.reserved = 20;
console.log(seminar.remainingSeats); // 10
La ligne du bas lève une exception parce qu'un nom qui n'a qu'un getter n'a nulle part où mettre la valeur affectée, et aucune nouvelle clé n'est créée sur l'instance non plus. Le tableau ci-dessous reprend les façons de faire utilisées dans le Seminar de cet article, avec une méthode pour comparer.
| Façon de faire | Côté appelant | Adaptée pour |
|---|---|---|
| Propriété simple (reserved) | seminar.reserved = 20, stocké tel quel | Valeurs sans vérification |
| Getter seul (remainingSeats) | Lecture seule : seminar.remainingSeats | Valeurs calculées |
| Getter et setter (capacity) | seminar.capacity = 40 est vérifié | Valeurs à vérifier à chaque affectation |
| Méthode (ex. reserve(count)) | Parenthèses et arguments | Résultats qui dépendent des arguments |
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Si set capacity contient this.capacity = value; et que tu affectes 40, que se passe-t-il ?
Question 3Que se passe-t-il si tu affectes 100 à seminar.remainingSeats, qui n'a qu'un getter ?