Question 1Après class Coupon {}, que se passe-t-il si tu appelles Coupon("SPRING") sans new ?
Les bases des classes — définir une classe et créer des instances
Comment une classe permet de créer beaucoup d'objets de même forme : créer des instances avec constructor et new, écrire des méthodes qui utilisent this, et savoir quand un littéral objet suffit.
Membres, produits, coupons : dans une application, les données qui ont les mêmes champs et servent aux mêmes calculs reviennent sans cesse. Si tu écris chacune sous forme de littéral objet, tu recopies les champs et la formule de calcul pour chaque élément.
Cet article présente les classes (class), qui réunissent en un seul endroit la forme des données et le code qui les traite, et montre comment créer les objets un par un avec new.
Définir la forme des données en un seul endroit — class et new
Prenons deux types de coupons de réduction. Si tu écris { code: "WELCOME", discount: 500 } pour chaque coupon et que tu tapes discont par erreur dans un seul d'entre eux, aucune erreur n'est levée : seul le montant de réduction de ce coupon vaut undefined. Comme les noms de clés sont réécrits dans chaque coupon, il te reste à les passer tous en revue pour trouver la faute de frappe.
Une classe (la définition d'un type d'objet que l'on crée avec new) s'écrit class Coupon { ... }, avec à l'intérieur un constructor (la partie appelée par new), sans le mot-clé function.
Dans le constructor, this est la nouvelle instance (un objet créé à partir d'une classe), et new la renvoie même sans return.
// Définir la forme d'un coupon en un seul endroit avec une classe
class Coupon {
constructor(code, discount) {
this.code = code; // À gauche, la propriété de l'instance ; à droite, l'argument reçu
this.discount = discount;
}
}
// Créer les coupons un par un avec new
const welcome = new Coupon("WELCOME", 500);
const summer = new Coupon("SUMMER", 1000);
console.log(welcome.code); // WELCOME
console.log(summer.discount); // 1000
console.log(welcome instanceof Coupon); // true
// Sans new, l'appel lève une erreur
// Coupon("SPRING", 300); // TypeError
this à l'intérieur du constructor. Sans new, l'appel échoue avant même d'entrer dans le constructor.Les noms de clés n'apparaissent qu'à un seul endroit, dans le constructor : une faute de frappe comme discont ne peut se trouver que là, et c'est le seul endroit à corriger. Dans le cas de la colonne de droite, la console affiche TypeError: Class constructor Coupon cannot be invoked without 'new'.
Calculer avec les valeurs d'une instance — les méthodes et this
Si tu calcules le montant après réduction avec total - welcome.discount à la fois sur l'écran du panier et sur celui de confirmation de commande, chaque changement de règle de calcul t'oblige à corriger deux endroits. Même si tu déplaces ce calcul dans une fonction, les champs restent dans la classe et le calcul se trouve ailleurs.
Une méthode (une fonction qui appartient à un objet et qu'on appelle avec un point, comme welcome.apply(3000)) s'écrit apply(total) { ... } entre les accolades de la classe, là encore sans function. Dans une méthode, this désigne l'instance écrite à gauche du point, et this.discount lit sa valeur au moment de l'appel.
class Coupon {
constructor(code, discount) {
this.code = code;
this.discount = discount;
}
// Soustrait la réduction de ce coupon du total et renvoie le résultat
apply(total) {
return total - this.discount; // this est l'instance à gauche du point
}
}
// Crée deux types de coupons
const welcome = new Coupon("WELCOME", 500);
const summer = new Coupon("SUMMER", 1000);
// Le même apply donne un résultat différent selon l'instance à gauche du point
console.log(welcome.apply(3000)); // 2500
console.log(summer.apply(3000)); // 2000
this est la variable écrite à gauche de .apply. Avec le même argument, un this différent donne un résultat différent.- Arguments
code/discount— utilisables seulement dans ces accolades this.discount = discount— stocke la valeur dans l'instance
this.discount— lit la valeur stockée dans l'instancetotal - discount— nom invisible, doncReferenceError
discount n'est plus visible hors des accolades du constructor. apply lit à la place la valeur stockée dans this.Si tu oublies this. dans apply et que tu écris total - discount, JavaScript cherche le nom discount en partant des accolades les plus intérieures vers l'extérieur. Le résultat dépend donc de l'existence, ou non, d'une variable du même nom en dehors de la classe.
Oublier this. ne lève pas toujours d'erreur
S'il existe hors de la classe une variable du même nom, comme const discount = 300;, le total - discount sans this. ne lève pas de ReferenceError : il utilise ces 300 sans rien signaler. Si tous les coupons renvoient le même montant, quel que soit celui sur lequel tu appelles la méthode, soupçonne un this. oublié.
Chaque instance garde ses propres valeurs — plusieurs instances
Chaque fois qu'un coupon sert, son nombre d'utilisations restantes diminue de 1. Si tu gardes ces nombres hors de la classe, dans un objet comme { WELCOME: 3, SUMMER: 3 }, chaque écran qui utilise un coupon doit retrouver son compteur à partir de code et y réécrire la nouvelle valeur.
Dans une méthode, tu peux aussi affecter une valeur à une propriété de this, comme dans this.remaining = this.remaining - 1. La partie droite lit la valeur actuelle, et le résultat, diminué de 1, est réécrit dans le remaining de la même instance. Le compteur part des 3 passés à new et diminue de 1 à chaque appel.
class Coupon {
constructor(code, remaining) {
this.code = code;
this.remaining = remaining; // Nombre d'utilisations restantes
}
// Retire une utilisation à l'instance à gauche du point
use() {
this.remaining = this.remaining - 1;
}
}
// Deux coupons avec le même compteur, utilisés un nombre de fois différent
const welcome = new Coupon("WELCOME", 3);
const summer = new Coupon("SUMMER", 3);
welcome.use();
welcome.use();
summer.use();
console.log(welcome.remaining); // 1
console.log(summer.remaining); // 2
Tu peux ranger des instances dans un tableau aussi bien que dans des variables. Quand tu appelles une méthode sur un élément, comme coupons[0].use(), this désigne cet élément. Quel que soit le nombre de coupons, le code qui décrémente le compteur tient en une seule ligne, dans use().
Choisir selon le nombre d'objets — classe ou littéral objet
Pour des données qui ne servent qu'une fois, comme les paramètres d'affichage d'un écran, écrire { ... } sur place suffit. Mais pour des données comme les coupons, que tu crées en grand nombre avec la même forme et le même calcul, les littéraux t'obligent à recopier jusqu'au corps du calcul dans chacun.
Un littéral objet peut lui aussi avoir des méthodes : écris apply(total) { ... } à côté des paires clé-valeur, et this y désigne cet objet. Ce qui change par rapport à une classe, c'est l'endroit où est écrit le corps de la méthode, et cette différence se voit dans le résultat de Object.keys.
// class : le corps est écrit une seule fois, dans la définition
class Coupon {
constructor(code, discount) {
this.code = code;
this.discount = discount;
}
apply(total) { return total - this.discount; }
}
const welcome = new Coupon("WELCOME", 500);
// Littéral : le corps est réécrit dans chaque objet
const summer = {
code: "SUMMER",
discount: 1000,
apply(total) { return total - this.discount; },
};
// Les clés ne sont pas les mêmes
console.log(Object.keys(welcome).join(", ")); // code, discount
console.log(Object.keys(summer).join(", ")); // code, discount, apply
Le corps n'est pas au même endroit, mais tu appelles welcome.apply(3000) exactement comme summer.apply(3000) : le code appelant ne change pas. Comment une instance peut appeler une méthode dont le corps se trouve dans la classe, c'est le sujet de l'article sur les prototypes. Le tableau ci-dessous compare les deux approches.
| Point comparé | Littéral objet | Classe |
|---|---|---|
| Objets créés | Seul le { } écrit | Un par new |
| Corps des méthodes | Recopié partout | Écrit une fois |
| Clés d'Object.keys | code, discount, apply | code, discount |
| instanceof Coupon | false | true |
| Adapté pour | Config ou réponse unique | Nombreux objets |
Pas de virgule après les méthodes d'une classe
Quand tu transformes un littéral en classe, si tu laisses la virgule après une méthode, comme dans apply(total) { ... },, tu obtiens SyntaxError: Unexpected token ',' et aucune ligne ne s'exécute. Entre les accolades d'une classe, les méthodes se suivent sans séparateur.
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Une variable discount valant 300 existe hors de la classe. Que renvoie welcome.apply(3000) si this. manque ?
Question 3Que donne Object.keys sur une instance d'une classe qui a constructor(code, discount) et apply ?