Classes
Classes
Classes
Les classes JavaScript ont été introduites avec ECMAScript 2015. Elles sont un « sucre
syntaxique » par rapport à l'héritage prototypal. En effet, cette syntaxe n'introduit pas un
nouveau modèle d'héritage dans JavaScript ! Elle fournit uniquement une syntaxe plus simple
pour créer des objets et manipuler l'héritage.
Pour utiliser une déclaration de classe simple, on utilisera le mot-clé class, suivi par le nom
de la classe que l'on déclare (ici « Rectangle »).
class Rectangle {
constructor(hauteur, largeur) {
this.hauteur = hauteur;
this.largeur = largeur;
}
}
Remontée des déclarations (hoisting)
Les déclarations de fonctions sont remontées dans le code. En revanche, ce n'est pas le cas
pour les déclarations de classes. Ainsi, il est nécessaire de déclarer la classe avant de
l'instancier. Dans le cas contraire, on obtient une ReferenceError :
class Rectangle {}
// anonyme
let Rectangle = class {
constructor(hauteur, largeur) {
this.hauteur = hauteur;
this.largeur = largeur;
}
};
// nommée
let Rectangle = class Rectangle {
constructor(hauteur, largeur) {
this.hauteur = hauteur;
this.largeur = largeur;
}
};
Note : Les mêmes règles s'appliquent aux expressions de classes en ce qui concerne la
remontée (hoisting) des classes (cf. le paragraphe précédent sur les remontées des déclarations
de classe).
Mode strict
Le corps des classes, pour les expressions et pour les déclarations de classes, est exécuté en
mode strict (autrement dit, le constructeur, les méthodes statiques, le prototype, les accesseurs
(getters) et mutateurs (setters) sont exécutés en mode strict).
Constructeur
La méthode constructor est une méthode spéciale qui permet de créer et d'initialiser les
objet créés avec une classe. Il ne peut y avoir qu'une seule méthode avec le nom "constructor"
pour une classe donnée. Si la classe contient plusieurs occurences d'une méthode
constructor, cela provoquera une exception SyntaxError.
Le constructeur ainsi déclaré peut utiliser le mot-clé super afin d'appeler le constructeur de la
classe parente.
Méthodes de prototype
class Rectangle {
constructor(hauteur, largeur) {
this.hauteur = hauteur;
this.largeur = largeur;
}
get area() {
return this.calcArea();
}
calcArea() {
return this.largeur * this.hauteur;
}
}
console.log(carré.area);
Méthodes statiques
Le mot-clé static permet de définir une méthode statique pour une classe. Les méthodes
statiques sont appelées par rapport à la classe entière et non par rapport à une instance donnée
(ces méthodes ne peuvent pas être appelées « depuis » une instance). Ces méthodes sont
généralement utilisées sous formes d'utilitaires au sein d'applications.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
console.log(Point.distance(p1, p2));
Lorsqu'une méthode statique ou une méthode liée au prototype est appelée sans valeur this,
celle-ci vaudra undefined au sein de la fonction. Il n'y aura pas d'autodétermination de this
(autoboxing en anglais). On aura le même résultat si on invoque ces fonctions dans du code
non-strict car les fonctions liées aux classes sont exécutées en mode strict.
class Animal {
crie() {
return this;
}
static mange() {
return this;
}
}
Si on écrit le code avec des fonctions traditionnelles plutôt qu'avec des classes et qu'on utilise
le mode non-strict, l'autodétermination de this sera faite en fonction du contexte dans lequel
la fonction a été appelée. Si la valeur initiale est undefined, this correspondra à l'objet
global.
L'autodétermination de this n'a pas lieu en mode strict, la valeur this est passée telle quelle.
function Animal() { }
Animal.prototype.crie = function() {
return this;
}
Animal.mange = function() {
return this;
}
Les propriétés des instances doivent être définies dans les méthodes de la classe :
class Rectangle {
constructor(hauteur, largeur) {
this.hauteur = hauteur;
this.largeur = largeur;
}
}
Les propriétés statiques ou les données relatives au prototype doivent être définies en dehors
de la déclaration comportant le corps de la classe :
Rectangle.largeurStatique = 20;
Rectangle.prototype.largeurProto = 25;
Déclarations de champs
En utilisant la syntaxe pour la déclaration des champs, on peut réécrire l'exemple précédent de
la façon suivante :
class Rectangle {
hauteur = 0;
largeur;
constructor(hauteur, largeur) {
this.hauteur = hauteur;
this.largeur = largeur;
}
}
En déclarant les champs en préalable, il est plus facile de comprendre la classe dans son
ensemble. De plus, on s'assure que les champs soient toujours présents.
Comme on peut le voir dans cet exemple, les champs peuvent éventuellement être déclarés
avec une valeur par défaut.
class Rectangle {
#hauteur = 0;
#largeur;
constructor(hauteur, largeur){
this.#hauteur = hauteur;
this.#largeur = largeur;
}
}
Si on utilise les champs privés hors de la classe, cela génèrera une erreur. Ces champs ne
peuvent être lus ou modifiés que depuis le corps de la classe. En évitant d'exposer des
éléments à l'extérieur, on s'assure que les portions de code qui consomment cette classe
n'utilise pas ses détails internes et on peut alors maintenir la classe dans son ensemble et
modifier les détails internes si besoin.
Note : Les champs privés doivent nécessairement être déclarés en premier dans les
déclarations de champ.
Il n'est pas possible de créer des champs privés a posteriori au moment où on leur affecterait
une valeur. Autrement dit, il est possible de déclarer une variable normale au moment voulu
lorsqu'on lui affecte une valeur tandis que pour les champs privés, ces derniers doivent être
déclarés (éventuellement initialisés) en amont, au début du corps de la classe.
class Animal {
constructor(nom) {
this.nom = nom;
}
parle() {
console.log(`${this.nom} fait du bruit.`);
}
}
class Chien extends Animal {
constructor(nom) {
super(nom); // appelle le constructeur parent avec le paramètre
}
parle() {
console.log(`${this.nom} aboie.`);
}
}
Si on déclare un constructeur dans une classe fille, on doit utiliser super() avant this.
On peut également étendre des classes plus traditionnelles basées sur des constructeurs
fonctionnels :
En revanche, les classes ne permettent pas d'étendre des objets classiques non-constructibles.
Si on souhaite créer un lien d'héritage en un objet et une classe, on utilisera
Object.setPrototypeOf() :
const Animal = {
crie() {
console.log(`${this.nom} fait du bruit.`);
}
};
class Chien {
constructor(nom) {
this.nom = nom;
}
crie() {
super.crie();
console.log(`${this.nom} aboie.`);
}
}
Object.setPrototypeOf(Chien.prototype, Animal);
Par exemple, si, lorsqu'on utilise des méthodes comme map() qui renvoient le constructeur
par défaut et qu'on veut qu'elles renvoient Array plutôt que MonArray, on utilisera
Symbol.species :
class Chat {
constructor(nom) {
this.nom = nom;
}
parler() {
console.log(`${this.nom} fait du bruit.`);
}
}
Les mix-ins
Les sous-classes abstraites ou mix-ins sont des modèles (templates) pour des classes. Une
classe ECMAScript ne peut avoir qu'une seule classe parente et il n'est donc pas possible, par
exemple, d'hériter de plusieurs classes dont une classe abstraite. La fonctionnalité dont on
souhaite disposer doit être fournie par la classe parente.
Une fonction peut prendre une classe parente en entrée et renvoyer une classe fille qui étend
cette classe parente. Cela peut permettre d'émuler les mix-ins avec ECMAScript.
Une classe utilisant ces mix-ins peut alors être écrite de cette façon :
class Toto { }
class Truc extends calculetteMixin(aleatoireMixin(Toto)) { }
Spécifications
Specification
ECMAScript Language Specification
# sec-class-definitions