Apprenez en lisant dans l'ordre

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
Stocker les places restantes ou les calculer à chaque lecture ?
Places restantesstockées dansle constructorStocke 30 - 0à la créationAffecte 12à reservedLa lecturedonne encore 30Places restantesdéfinies avec getNe stocke rienà la créationAffecte 12à reserved30 - 12 à lalecture : 18
Les deux lignes font la même affectation avant la lecture. Un getter calcule avec la valeur de reserved au moment de la lecture.

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.

Une carte continue d'afficher les anciens prix TTC après une hausse de prix. Corrige-la sans toucher au code qui lit les prix. MenuItem et menu sont déjà déclarés.

① Remplace priceWithTax, calculé dans le constructor, par une propriété recalculée à chaque lecture.

② Augmente de 40 yens le prix hors taxes de chaque article.

③ Affiche « Nom de l'article : X yens TTC » pour chaque article.

④ Affiche « Taxe : X yens » pour le premier article.

(Si le code s'exécute correctement, une explication s'affiche.)

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

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
La méthode exécutée à l'affectation et à la lecture
get capacityset capacity(value)① seminar.capacity= 40② Lecture deseminar.capacity③ seminar.capacity= 5set s'exécute :40 est ≥ 12get s'exécute etrenvoie _capacityset s'exécute :5 est < 12_capacitypasse à 40Renvoie 40throw ;reste à 40
Quand la propriété est à gauche de =, 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.

Tu appliques à la température de consigne les saisies faites sur le panneau de commande d'un climatiseur. AirConditioner et temperatureInputs sont déjà déclarés.

① Ajoute une propriété temperature qui lit et modifie la température de consigne, et qui lève l'exception « La température doit être comprise entre 16 et 30 degrés : X » si la valeur est hors plage.

② Affecte les saisies dans l'ordre, et affiche « Réglée sur X degrés » si la valeur est appliquée, ou sinon le message de l'exception.

③ Affiche « Nom de la pièce : en marche à X degrés » avec la dernière température de consigne.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

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
}
Le constructor appelle le setter
Corps du try (new est appelé ici)
  • new Seminar(5, 12) — appelle le constructor
  • L'exception du setter remonte jusqu'ici, aucune instance n'est renvoyée
Corps du constructor appelé
  • this.reserved = 12 — stocke les inscriptions avant la vérification
  • this.capacity = 5 — le nom n'a pas de _, donc appelle set capacity
Corps de set capacity(value) appelé
  • value vaut 5 et this.reserved vaut 12
  • 5 < 12, donc throw — rien n'entre dans _capacity
La ligne 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.

Tu enregistres le nom complet des employés en séparant le prénom et le nom. staffNames est déjà déclaré.

① Définis Employee, qui reçoit un nom complet et stocke le prénom et le nom séparément.

② Ajoute fullName, qui lit et modifie « Prénom Nom » et lève l'exception « Le prénom et le nom doivent être séparés par un espace : X » s'il n'y a pas d'espace.

③ Crée les employés un par un, et affiche le message de l'exception pour ceux qui ne peuvent pas être créés.

④ Affecte « Alice Dubois » au fullName du premier employé, puis affiche le prénom, le nom et le nom complet de chacun.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

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
Une affectation des places restantes : acceptée ou refusée ?
Places restantesstockées en valeurremainingSeats= 100100 est stockétel quelIncohérent :capacité 30,12 inscritsPlaces restantesavec get seulremainingSeats= 100Pas de set,donc TypeErrorReste à30 - 12 = 18
Sur la ligne du haut, l'affectation passe et laisse une valeur qui ne correspond ni à la capacité ni aux inscriptions. Avec un getter seulement, l'affectation est refusée sur-le-champ.

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 faireCôté appelantAdaptée pour
Propriété simple (reserved)seminar.reserved = 20, stocké tel quelValeurs sans vérification
Getter seul (remainingSeats)Lecture seule : seminar.remainingSeatsValeurs 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 argumentsRésultats qui dépendent des arguments
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Avec un getter qui renvoie 30 - this.reserved, qu'obtiens-tu si tu mets reserved à 20 puis lis le getter ?

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 ?