Apprenez en lisant dans l'ordre

constructor et champs de classe — définir les valeurs initiales

Comment le constructor et les champs de classe fixent les valeurs initiales d'une instance : stocker les arguments, l'ordre d'ajout des champs, les arguments omis et le rejet des valeurs invalides.

Un ticket de support contient des valeurs qui changent d'un ticket à l'autre, comme son titre, et des valeurs initiales identiques pour tous, comme l'état « En attente ». Si la classe est mal écrite, des valeurs omises ou invalides se retrouvent stockées dans l'instance.

Cet article traite du constructor, qui reçoit les valeurs passées à la création, et des champs de classe, qui déclarent des valeurs initiales fixes.

Nettoyer les arguments avant de les stocker — le corps du constructor

Les titres envoyés depuis le formulaire de support contiennent parfois des espaces en trop au début ou à la fin. Si tu construis une chaîne d'affichage comme « Alice : Connexion impossible » après la création de l'instance, chaque écran qui l'affiche a besoin de son propre trim et de son propre code d'assemblage.

Dans le corps du constructor, rien ne t'oblige à stocker l'argument tel qu'il arrive. Tu peux stocker une version nettoyée, comme dans this.title = title.trim(), ou une valeur construite à partir de plusieurs arguments. Chaque écran n'a plus qu'à lire les propriétés de l'instance pour obtenir des valeurs déjà propres.

class Ticket {
  constructor(title, customerName) {
    // Nettoie l'argument avant de le stocker dans this
    this.title = title.trim();
    this.customerName = customerName;

    // Construit la valeur affichée à partir de this, après le nettoyage
    this.label = `${this.customerName} : ${this.title}`;
    // Avec `${customerName} : ${title}`, les espaces du titre resteraient
  }
}

// Passe un titre entouré d'espaces
const ticket = new Ticket("  Connexion impossible ", "Alice");
console.log(ticket.title);    // Connexion impossible
console.log(ticket.label);    // Alice : Connexion impossible
D'où vient la valeur de label
Construit avecl'argument titletitle garde lesespaces autourIntègre aussiles espacesEspaces en tropaprès « Alice : »Construit avecthis.titlethis.title estdéjà nettoyéIntègre le titrenettoyéAlice : Connexionimpossible
Aucune des deux lignes ne lève d'erreur ; la différence n'apparaît qu'à l'affichage. Pour utiliser la valeur nettoyée, relis-la dans this une fois stockée.

La ligne du haut lit l'argument title, celle du bas lit this.title après trim. Si tu déplaces la ligne de label au début du constructor, ni this.title ni this.customerName n'ont encore été affectés : label vaut alors undefined : undefined.

Crée des données de réservation à partir des valeurs d'un formulaire. selectedRoom, selectedStart et selectedHours sont déjà déclarés.

① Définis Reservation pour que chaque instance contienne un nom de salle, une heure de début et une durée.

② Dans le constructor, stocke aussi l'heure de fin dans endHour.

③ Passe les trois valeurs pour créer booking.

④ Affiche-la sous la forme « Salle : de N h à N h (N heures) ».

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

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Déclarer les valeurs initiales fixes — les champs de classe

Tout nouveau ticket démarre avec l'état « En attente » et zéro commentaire. Si tu définis ces valeurs initiales, qui n'ont rien à voir avec les arguments, dans le constructor avec this.status = "En attente";, elles se mélangent aux lignes qui stockent les arguments, et il faut lire tout le corps pour savoir ce que contient une instance.

Un champ de classe (une propriété déclarée dans le corps de la classe, hors des méthodes, et ajoutée à chaque instance lors du new) s'écrit avec un nom et une valeur initiale seulement, sans this, comme status = "En attente";. Pour le lire depuis le corps du constructor ou depuis une méthode, tu écris this.status, comme pour n'importe quelle autre propriété.

class Ticket {
  // Les valeurs initiales indépendantes des arguments vont dans des champs
  status = "En attente";
  comments = [];

  constructor(title) {
    this.title = title;
    this.summary = `[${this.status}] ${title}`;   // Les valeurs des champs sont déjà lisibles
  }
}

const first = new Ticket("Connexion impossible");
const second = new Ticket("Facture non reçue");
console.log(first.summary);                // [En attente] Connexion impossible

// comments est un tableau distinct, créé à chaque new
first.comments.push("Client invité à redémarrer");
console.log(first.comments.length);        // 1
console.log(second.comments.length);       // 0
L'ordre des étapes dans new Ticket
new Ticket("Connexionimpossible")① Champs ajoutésdans l'ordre② Corps exécuté,lit this.status③ L'instance vadans firstL'instanceest videstatus:"En attente"comments: []title et summarysont ajoutésElle a maintenant4 propriétés
Dans new, les champs sont ajoutés d'abord, puis le corps du constructor s'exécute. Le corps peut donc déjà lire les valeurs des champs.

L'étape ① est refaite à chaque new : comments = [] devient donc un tableau distinct pour chaque instance, et un commentaire ajouté à first ne se retrouve pas dans second. C'est pour cela que second.comments.length reste à 0.

this. devant un champ : SyntaxError

Si tu écris this.status = "En attente"; à l'endroit des champs (hors du constructor), tu obtiens SyntaxError: Unexpected token '.' et aucune ligne ne s'exécute. Ajouter let ou const provoque aussi une SyntaxError. Un champ, c'est seulement un nom et une valeur initiale.

Crée des données d'expédition pour chaque destination. destinations est déjà déclaré.

① Définis Shipment, qui reçoit une destination et démarre avec des frais de port de 600 yens et l'état « Enregistrée ».

② Dans le constructor, passe les frais à 1500, uniquement quand la destination est « Okinawa ».

③ Crée un Shipment pour chaque destination et range-les dans le tableau shipments.

④ Affiche chacun sous la forme « Destination : état / frais de port N yens ».

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat

Choisir la valeur d'un argument omis — les paramètres par défaut

La plupart des tickets ont la priorité « Normale », et tu veux passer « Urgente » seulement quand c'est pressé. Si tu écris le champ priority = "Normale"; et que tu ajoutes aussi this.priority = priority; dans le corps, un ticket créé sans priorité ne se retrouve pas avec « Normale ».

Un paramètre par défaut (la valeur utilisée quand un argument n'est pas passé), déjà vu dans l'article sur les paramètres par défaut et les paramètres du reste, fonctionne aussi dans un constructor : écris priority = "Normale" dans la liste des paramètres. Un argument omis ou undefined est remplacé par la valeur par défaut avant que le corps s'exécute : this.priority = priority reçoit donc « Normale ».

// Une valeur par défaut dans le champ, et l'argument affecté dans le corps
class DraftTicket {
  priority = "Normale";
  constructor(title, priority) {
    this.title = title;
    this.priority = priority;   // À droite, l'argument priority (undefined s'il est omis)
  }
}
console.log(new DraftTicket("Connexion impossible").priority);      // undefined

// Pour une valeur qu'un argument peut changer, utilise un paramètre par défaut
class Ticket {
  constructor(title, priority = "Normale") {
    this.title = title;
    this.priority = priority;
  }
}
console.log(new Ticket("Connexion impossible").priority);           // Normale
console.log(new Ticket("Facture non reçue", "Urgente").priority);   // Urgente
Comment le « Normale » du champ se perd
new DraftTicket("Connexionimpossible")① Ajout deschamps② Le corps affectethis.title③ Le corps affectethis.priorityL'argumentpriority vautundefinedthis.priorityvaut "Normale"this.priority vauttoujours "Normale"this.priorityvaut undefined
Le « Normale » du champ est posé avant le corps, puis l'affectation du corps l'écrase avec l'undefined de l'argument. C'est la valeur affectée dans le corps qui reste à la fin.

Mets dans un paramètre par défaut une valeur initiale que l'appelant peut remplacer, et dans un champ celle qui ne dépend pas des arguments. Pour une valeur qui dépend d'une condition sur les arguments, comme les frais de port de l'exercice 2, utilise un if dans le corps pour modifier la valeur initiale du champ. Le tableau ci-dessous montre ce qui reste dans priority selon l'approche, avec ou sans « Urgente », y compris une version qui ne l'affecte jamais dans le corps.

ApprocheTitre seulAvec « Urgente »
Champ seul (sans affectation)NormaleNormale (argument ignoré)
Champ + this.priority = priorityundefinedUrgente
Paramètre priority = "Normale"NormaleUrgente

Refuser les valeurs invalides — throw dans le constructor

Si tu te retrouves avec un ticket au titre vide, ou avec une priorité qui ne fait pas partie des valeurs prévues, comme « Haute », la liste affiche une ligne vide, et le ticket est oublié quand tu comptes les tickets « Urgente ». Même si tu écris une fonction séparée pour vérifier les tickets après leur création, tout écran qui oublie de l'appeler continue d'utiliser l'instance invalide.

Pour la validation à la création (vérifier, au moment où tu crées une instance, que les valeurs reçues respectent les règles), écris un if dans le constructor et lève une exception avec throw new Error(...) quand une valeur n'est pas conforme. Une fois l'exception levée, l'expression new ne renvoie aucune instance.

class Ticket {
  constructor(title, priority = "Normale") {
    // Rejette les valeurs invalides par une exception, avant qu'elles atteignent this
    if (title.trim() === "") {
      throw new Error("Le titre est vide");
    }
    if (priority !== "Normale" && priority !== "Urgente") {
      throw new Error(`La priorité doit être Normale ou Urgente : ${priority}`);
    }
    this.title = title.trim();
    this.priority = priority;
  }
}

try {
  const ticket = new Ticket("Connexion impossible", "Haute");
  console.log(ticket.title);    // Cette ligne ne s'exécute jamais
} catch (error) {
  console.log(error.message);   // La priorité doit être Normale ou Urgente : Haute
}
Où va un throw levé dans new
Dans try (new est appelé ici)
  • const ticket = new Ticket(...) — la partie droite lève une exception, ticket ne reçoit rien
  • console.log(ticket.title) — ne s'exécute jamais
Corps du constructor appelé
  • Vérification du titre — non vide, elle passe
  • Vérification de la priorité — elle vaut "Haute", donc throw
  • this.title = ... — plus rien ne s'exécute à partir d'ici
Dans catch (error)
  • error.message — « La priorité doit être Normale ou Urgente : Haute »
L'exception sort de l'expression new, saute le reste du try et arrive dans catch. L'instance invalide n'arrive dans aucune variable.

Avec la vérification dans le constructor, aucun ticket ne peut y échapper, quel que soit le nombre d'endroits qui appellent new Ticket(...). Si tu inclus la valeur refusée dans le message de l'exception, le bloc catch peut aussi afficher quelle valeur était invalide.

Les affectations après la création ne sont pas vérifiées

La vérification du constructor ne s'exécute que pendant new. Si tu écris ticket.priority = "Haute"; après la création du ticket, aucune erreur ne survient et la valeur est simplement remplacée. L'article sur les getters et setters montre comment vérifier la valeur à chaque affectation.

N'accepte que les lignes de commande qui respectent les règles. requests et acceptedLines sont déjà déclarés.

① Définis OrderLine, qui reçoit un nom de produit et une quantité (1 si aucune quantité n'est fournie).

② Lève une exception avec « Le nom du produit est vide » si le nom du produit est vide, ou avec « Quantité invalide : N » si la quantité n'est pas un entier supérieur ou égal à 1.

③ Transforme chaque demande en OrderLine. Si la création réussit, ajoute-la à acceptedLines et affiche « Accepté : nom x quantité » ; sinon, affiche « Refusé : message de l'exception ».

④ Affiche le nombre de lignes acceptées sous la forme « Lignes acceptées : N ».

Éditeur JavaScript / TypeScript

Exécuter le code pour voir le résultat
QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Dans le constructor d'une classe qui a status = "En attente";, que donne la lecture de this.status ?

Question 2Une classe n'a que le champ priority = "Normale"; et ne l'affecte pas dans le corps. Que se passe-t-il si tu passes « Urgente » ?

Question 3Dans un try, le constructor lève une exception pendant const ticket = new Ticket(""). Que se passe-t-il ?