Apprenez en lisant dans l'ordre

Membres d'instance et static — les valeurs et méthodes de la classe

Comment static place des valeurs et des méthodes sur la classe elle-même : compteurs, méthodes de fabrique à partir de JSON, constantes partagées, et quand préférer les membres d'instance.

Pour numéroter des colis 1, 2, 3 dans l'ordre de leur création, il faut garder quelque part le nombre de colis déjà créés. Le destinataire change d'un colis à l'autre, mais ce compteur et le poids maximal n'ont besoin d'exister qu'une fois, pour tous les colis.

Cet article traite de static, qui place des valeurs et des méthodes sur la classe elle-même plutôt que sur ses instances.

Compter les colis — les champs static

Si tu écris le champ de classe count = 0;, que tu l'augmentes avec this.count += 1 dans le constructor et que tu t'en sers comme numéro, tous les colis reçoivent le numéro 1 : le champ repart de 0 à chaque new.

Un champ static (une propriété déclarée dans la classe avec le mot-clé static, qui n'existe qu'en un seul exemplaire, sur la classe elle-même) se lit et s'écrit via le nom de la classe, comme Parcel.count.

À l'inverse, les propriétés que chaque instance possède, comme this.number, et les méthodes écrites sans static s'appellent des membres d'instance.

class Parcel {
  // Nombre de colis créés jusqu'ici, stocké une seule fois sur la classe
  static count = 0;

  constructor(recipient) {
    Parcel.count += 1;               // Ajoute 1 au count de la classe
    this.number = Parcel.count;      // Le count à cet instant devient le numéro du colis
    this.recipient = recipient;
  }
}

// Crée deux colis
const first = new Parcel("Alice");
const second = new Parcel("Léa");

console.log(first.number, second.number);   // 1 2
console.log(Parcel.count);                  // 2
console.log(first.count);                   // undefined
Où se trouvent count et number
Parcel (la classe elle-même)
  • static count — 1 après le premier new, 2 après le second
  • Se lit et s'écrit avec Parcel.count
Instances créées avec new
first
  • number — 1 (le count à la création)
  • recipient — « Alice »
  • Pas de countfirst.count vaut undefined
second
  • number — 2 (le count à la création)
  • recipient — « Léa »
Il n'y a qu'un seul count, dans la boîte de la classe, et aucun dans celles des instances. Le lire depuis une instance donne undefined.

Chaque new augmente de 1 le même count, celui de la boîte de la classe, et copie sa valeur du moment dans le number de la nouvelle instance. Même quand count passe ensuite à 2, first.number reste à 1, la valeur reçue à sa création.

Numérote les réponses à un questionnaire dans l'ordre d'arrivée. answerTexts est déjà déclaré.

① Définis SurveyAnswer, qui reçoit le texte de la réponse. À chaque création, augmente d'une unité un compteur commun à toutes les réponses, et utilise-le comme numéro de la réponse.

② Transforme les trois textes en instances de SurveyAnswer et range-les dans answers.

③ Affiche chaque réponse sous la forme « A-001 texte de la réponse ».

④ Affiche « Réponses : N » et « Numéro de la première réponse : N ».

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

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Créer des colis à partir de JSON — les méthodes static

Quand tu recharges un colis enregistré, tu obtiens une chaîne JSON qui contient le destinataire et le poids. Si chaque écran qui charge des colis a son propre JSON.parse et son propre new Parcel(...), un changement des noms de clés du JSON t'oblige à corriger tous ces écrans.

Or, au moment de créer un colis à partir du JSON, il n'existe encore aucune instance sur laquelle appeler une méthode. Une méthode static (une méthode définie avec static, que l'on appelle via le nom de la classe sans créer d'instance) s'utilise sous la forme Parcel.fromJSON(text). On parle de méthode de fabrique (factory method) pour une méthode static qui, comme fromJSON, crée et renvoie une instance.

class Parcel {
  constructor(recipient, weightKg) {
    this.recipient = recipient;
    this.weightKg = weightKg;
  }

  label() {
    return `${this.recipient} (${this.weightKg} kg)`;
  }

  // Reçoit une chaîne JSON, puis crée et renvoie une instance de Parcel
  static fromJSON(text) {
    const data = JSON.parse(text);
    return new Parcel(data.recipient, data.weightKg);
  }
}

const text = '{"recipient":"Alice","weightKg":2}';
const parcel = Parcel.fromJSON(text);        // Appel via le nom de la classe
console.log(parcel.label());                 // Alice (2 kg)
D'une chaîne JSON à un Parcel
Parcel.fromJSON(text) est appeléAnalyse avecJSON.parse(text)Crée et renvoienew Parcel(…)text n'est qu'unechaîneA des propriétésmais pas label()Instance qui peutappeler label()
JSON.parse donne un objet simple, avec des propriétés mais sans méthodes. Ce n'est qu'après être passé par new qu'il devient une instance capable d'appeler label().

La dernière ligne de fromJSON est new Parcel(...) : le corps du constructor s'exécute donc normalement. Si tu places des vérifications avec throw dans le constructor, les colis rechargés depuis le JSON passent par les mêmes vérifications que ceux saisis à l'écran.

fromJSON ne s'appelle pas sur une instance

Si tu l'appelles sur une instance, comme parcel.fromJSON(text), une TypeError: parcel.fromJSON is not a function est levée. Contrairement à label(), une méthode static ne peut être appelée que via le nom de la classe. Le mécanisme est expliqué dans l'article sur les prototypes.

Recharge des articles de panier enregistrés à partir de chaînes JSON. CartItem et savedTexts sont déjà déclarés.

① Ajoute une méthode fromJSON qui crée et renvoie une instance de CartItem à partir d'une chaîne JSON.

② Avec fromJSON, transforme les deux chaînes de savedTexts en instances de CartItem.

③ Affiche « Nom x quantité = sous-total yens » pour chaque article.

④ Affiche la somme des sous-totaux sous la forme « Total : N yens ».

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Une limite fixe pour tous les colis — les constantes static

Le poids maximal de 30 kg sert à la fois dans le message du formulaire de saisie, affiché avant même qu'un colis existe, et dans le calcul de la capacité restante de chaque colis. Si tu mets cette limite dans un champ d'instance, il faut créer un colis sans destinataire ni poids rien que pour afficher le message du formulaire.

Une constante static (un champ static dont la valeur ne change pas pendant l'exécution) s'écrit par convention tout en majuscules, avec des tirets bas entre les mots, comme static MAX_WEIGHT_KG = 30;. Hors de la classe comme à l'intérieur d'une méthode, tu la lis via le nom de la classe : Parcel.MAX_WEIGHT_KG.

class Parcel {
  // La limite commune à tous les colis est stockée une seule fois sur la classe avec static
  static MAX_WEIGHT_KG = 30;

  constructor(weightKg) {
    this.weightKg = weightKg;
  }

  // Combien de kg on peut encore ajouter avant la limite
  remainingKg() {
    return Parcel.MAX_WEIGHT_KG - this.weightKg;
    // Avec this.MAX_WEIGHT_KG - this.weightKg, on obtiendrait NaN
  }
}

// Lisible avant même qu'une instance existe
console.log(`Jusqu'à ${Parcel.MAX_WEIGHT_KG} kg par colis`);   // Jusqu'à 30 kg par colis
const parcel = new Parcel(12);
console.log(parcel.remainingKg());                             // 18
La façon de lire la limite change le résultat
static MAX_WEIGHT_KG= 30Formulaire :Parcel.MAX_WEIGHT_KGDans remainingKg :Parcel.MAX_WEIGHT_KGDans remainingKg :this.MAX_WEIGHT_KGLit 30 sansinstanceCalcule30 - 12Absent del'instance :undefinedAffiche « Jusqu'à30 kg par colis »Renvoie 18undefined - 12renvoie NaN
Les colonnes de gauche et du milieu lisent le même 30, avec ou sans instance. Seule la colonne de droite, qui lit via this, aboutit à NaN.

La colonne de droite aboutit à NaN parce que this, dans une méthode, désigne l'instance, et que les constantes static ne se trouvent pas sur l'instance. undefined - 12 ne lève aucune erreur : tu ne t'en rends compte qu'en voyant un poids restant incohérent à l'affichage.

Un nom en majuscules n'empêche pas la réaffectation

Un nom en majuscules n'est qu'une convention qui signifie « ne pas modifier » : si tu écris Parcel.MAX_WEIGHT_KG = 50;, la valeur change. Si tu définis seulement un getter, comme static get MAX_WEIGHT_KG() { return 30; }, toute affectation lève une TypeError (les getters sont présentés dans l'article suivant).

Vérifie des noms d'utilisateur selon des règles de longueur. registeredNames est déjà déclaré.

① Donne à Account une longueur minimale de nom de 3 et une longueur maximale de 12, communes à tous les comptes.

② Ajoute isValidName, qui indique si la longueur du nom est dans l'intervalle.

③ Transforme chaque nom en Account, et affiche « nom : valide » ou « nom : invalide ».

④ Une autre partie du code fixe par erreur la longueur maximale à 20 : ajoute cette affectation, puis affiche à nouveau les résultats sous la même forme qu'en ③.

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Choisir où placer une valeur — static ou instance

Une fois que tu connais static, il peut sembler pratique de mettre aussi une valeur comme le destinataire sur la classe, pour la lire à un seul endroit, Parcel.recipient. Lors d'un test rapide qui ne crée qu'un seul colis, le même destinataire s'affiche qu'il soit en static ou dans this : tu ne remarques pas la différence.

Pour décider où placer une valeur, demande-toi si elle change d'un objet à l'autre. Une valeur placée dans this existe séparément dans chaque instance créée par new, alors qu'une valeur static n'existe qu'une fois, sur la classe, quel que soit le nombre de colis créés.

// Destinataire gardé en static
class SharedParcel {
  static recipient = "";
  constructor(recipient) { SharedParcel.recipient = recipient; }
  label() { return `Destinataire : ${SharedParcel.recipient}`; }
}

// Destinataire gardé dans l'instance
class Parcel {
  constructor(recipient) { this.recipient = recipient; }
  label() { return `Destinataire : ${this.recipient}`; }
}

const sharedFirst = new SharedParcel("Alice");
new SharedParcel("Léa");
console.log(sharedFirst.label());   // Destinataire : Léa

const first = new Parcel("Alice");
new Parcel("Léa");
console.log(first.label());         // Destinataire : Alice
Le 2e new change-t-il le destinataire du 1er colis ?
Destinataire dansstatic recipientLe 2e newaffecte LéaL'unique recipientest écrasésharedFirst.label()affiche LéaDestinataire dansthis.recipientLe 2e new crée uneautre instancerecipient du 1erreste Alicefirst.label()affiche Alice
Sur la ligne du haut, le destinataire du deuxième colis écrase l'unique valeur stockée sur la classe. Place dans this les valeurs qui changent d'un objet à l'autre, pour que chaque instance ait la sienne.

sharedFirst n'a pas de destinataire à lui, et label() lit SharedParcel.recipient tel qu'il est au moment de l'appel : il affiche donc « Léa », la dernière valeur affectée. Le tableau ci-dessous récapitule les membres utilisés pour les colis dans cet article : comment écrire chaque type et comment s'en servir.

Ce qu'on y metÉcriture et usageExemples ici
Valeur propre à chaque objetDans this, lue sur l'instancenumber / recipient / weightKg
Méthode lisant ces valeursSans static, appelée sur l'instancelabel() / remainingKg()
Valeur unique pour tousAvec static, lue via la classecount / MAX_WEIGHT_KG
Traitement sans instanceAvec static, appelé via la classefromJSON(text)
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Parcel a static count = 0;. Qu'obtiens-tu en lisant first.count sur une instance first ?

Question 2Que se passe-t-il si tu appelles une méthode static sur l'instance parcel, comme parcel.fromJSON(text) ?

Question 3Une classe garde le destinataire dans un champ static. Si tu crées un colis pour « Alice », puis un autre pour « Léa », quel destinataire affiche le label() du premier colis ?