🌐 AIæœçŽą & 代理 䞻饔
7 juillet 2023

Syntaxe de base de la Classe

En langage orientĂ© objet, une classe est un modĂšle de code programme extensible servant Ă  crĂ©er des objets. Elle fournit les valeurs initiales de l’état (les variables membres) et de l’implĂ©mentation du comportement (les fonctions ou mĂ©thodes membres).

Wikipedia

En pratique, nous avons souvent besoin de crĂ©er beaucoup d’objets de mĂȘme type, tels que des utilisateurs, des biens ou toute autre chose.

Comme nous le savons dans le chapitre Le constructeur, l'opérateur "new", new function peut nous aider à faire cela.

Mais dans le JavaScript moderne, il y a une construction de la “classe” plus avancĂ©e qui introduit de nombreux nouveaux aspects utiles en langage orientĂ© objet.

La syntaxe de “classe”

La syntaxe de base est :

class MyClass {
  // Les méthodes de la classe
  constructor() { ... }
  method1() { ... }
  method2() { ... }
  method3() { ... }
  ...
}

Vous pouvez ensuite utiliser new MyClass() pour créer un nouvel objet ayant toute la liste des méthodes.

La mĂ©thode constructor() est automatiquement appelĂ©e par new, donc nous pouvons initialiser l’objet Ă  ce niveau.

Par exemple :

class User {

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

  sayHi() {
    alert(this.name);
  }

}

// Usage:
let user = new User("John");
user.sayHi();

Lorsque new User("John") est appelé :

  1. Un nouvel objet est créé.
  2. Le constructor s’exĂ©cute avec les arguments qui lui sont passĂ©s et assigne this.name a l’objet.


ensuite nous pouvons appeler les mĂ©thodes de l’objet, tel que user.sayHi().

Pas de virgule entre les méthodes de la classe

Un piÚge fréquent des développeurs novices est de mettre une virgule entre les méthodes de la classe, entrainant ainsi une erreur syntaxique.

La notation ici ne doit pas ĂȘtre confondue avec les objets littĂ©raux. A l’intĂ©rieure d’une classe, aucune virgule n’est requise.

Qu’est-ce qu’une classe ?

Alors, c’est quoi exactement une class ? Ce n’est pas totalement une nouvelle entitĂ© au niveau du langage, comme on pourrait le penser.

DĂ©voilons maintenant la magie et regardons ce qu’est rĂ©ellement une classe. Cela va nous aider Ă  comprendre plusieurs aspects complexes.

En JavaScript, une classe est une sorte de fonction.

Regardons ici :

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// La preuve : User est une fonction
alert(typeof User); // function

Ce que la construction de la classe classe User {...} fait rĂ©ellement, c’est :

  1. CrĂ©er une fonction nommĂ©e User, qui devient le rĂ©sultat de la dĂ©claration de la classe. Le code de la fonction est tirĂ©e de la mĂ©thode constructor (considĂ©rĂ©e comme Ă©tant vide au cas ou cette mĂ©thode n’est pas Ă©crite).
  2. Garde les méthodes de la classe, telle que sayHi, dans User.prototype.

AprĂšs la crĂ©ation de new User, lorsque nous appelons sa mĂ©thode, elle est extraite du prototype, comme dĂ©crit dans le chapitre F.prototype. Donc, l’objet a accĂšs aux mĂ©thodes de classe.

Nous pouvons illustrer le résultat de la déclaration de class User ainsi :

Voici le code pour une introspection :

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// classe est une fonction
alert(typeof User); // function

// ...ou, plus précisément, le constructeur de la méthode
alert(User === User.prototype.constructor); // true

// Les méthodes sont dans User.prototype, par exemple :
alert(User.prototype.sayHi); // le code de la méthode sayHi

// Il y a exactement deux méthodes dans le prototype
alert(Object.getOwnPropertyNames(User.prototype)); // constructeur, sayHi

Pas simplement un sucre syntaxique

Parfois certaines personnes disent que la notion de class est un “sucre syntaxique” (une syntaxe qui est destinĂ©e Ă  rendre la lecture plus facile, mais elle n’introduit rien de nouveau), parce qu’en rĂ©alitĂ© nous pouvons dĂ©clarer la mĂȘme chose sans utiliser le mot clĂ© class :

// Réécriture de class User en fonctions pures

// 1. Créer la fonction constructeur
function User(name) {
  this.name = name;
}
// un prototype de fonction a une propriété constructeur par défaut,
// nous n'avons donc pas besoin de le créer

// 2. Ajouter la méthode au prototype
User.prototype.sayHi = function() {
  alert(this.name);
};

// Usage:
let user = new User("John");
user.sayHi();

Le rĂ©sultat de cette dĂ©finition est Ă  peu prĂšs la mĂȘme chose. Donc, il y a bien des raisons de vouloir considĂ©rer class comme pouvant ĂȘtre un sucre syntaxique pour dĂ©finir un constructeur ensemble avec ses mĂ©thodes de prototype.

Cependant, il existe des différences importantes.

  1. PremiĂšrement, une fonction créée par class est labellisĂ©e par une propriĂ©tĂ© interne spĂ©ciale [[IsClassConstructor]]: true. Ce n’est donc pas tout Ă  fait la mĂȘme chose que de le crĂ©er manuellement.

    Le langage vĂ©rifie cette propriĂ©tĂ© Ă  divers endroits. Par exemple, contrairement Ă  une fonction rĂ©guliĂšre, elle doit ĂȘtre appelĂ©e avec new :

    class User {
      constructor() {}
    }
    
    alert(typeof User); // fonction
    User(); // Erreur: le constructeur Class User ne peut ĂȘtre invoque sans 'new'

    Aussi, la reprĂ©sentation en chaĂźne de caractĂšres d’un constructeur de class dans la plupart des moteurs de JavaScript commence avec “class
” :

    class User {
      constructor() {}
    }
    
    alert(User); // class User { ... }

    Il y a d’autres diffĂ©rences, nous les verrons bientĂŽt.

  2. Les méthodes de Class sont non-énumérable. Une définition de la classe attribue false à la propriété enumerable pour toutes les méthodes du "prototype".

    C’est bien, parce que si nous exĂ©cutons un for..in sur un Object, souvent nous ne voulons pas accĂ©der aux mĂ©thodes de sa classe.

  3. Les Classes utilisent toujours use strict. Tout code Ă  l’intĂ©rieur de la construction de la classe est automatiquement en mode strict.

En outres, la syntaxe classe apporte beaucoup d’autres caractĂ©ristiques que nous allons explorer plus tard.

L’Expression Class

Tout comme les fonctions, les classes peuvent ĂȘtre dĂ©finies a l’intĂ©rieur d’une autre expression, passĂ©es en paramĂštres, retournĂ©es, assignĂ©es etc.

Voici un exemple d’expression d’une classe :

let User = class {
  sayHi() {
    alert("Hello");
  }
};

Similairement aux Fonction Expressions nommées, les expressions de classe peuvent avoir un nom.

Si une expression de classe a un nom, il est visible Ă  l’intĂ©rieur de la classe uniquement :

// "Expression de Classe nommée"
// (Terme non existant dans la spécification, mais elle est similaire a une Expression de Fonction nommée)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // le nom MyClass est seulement visible dans la classe
  }
};

new User().sayHi(); // ça fonctionne, montre la définition de MyClass

alert(MyClass); // erreur, le nom MyClass n'est pas visible en dehors de la classe

Nous pouvons mĂȘme crĂ©er les classes dynamiquement “à la demande”, comme suit :

function makeClass(phrase) {
  // déclare une classe et la retourne
  return class {
    sayHi() {
      alert(phrase);
    }
  };
}

// Crée une nouvelle classe
let User = makeClass("Hello");

new User().sayHi(); // Hello

Accesseurs/Mutateurs

Tout comme les objets littéraux, les classes peuvent inclure des accesseurs/mutateurs, des propriétés évaluées etc.

Voici un exemple pour user.name implémenté en utilisant les propriétés get/set :

class User {

  constructor(name) {
    // invoque l'accesseur (the setter)
    this.name = name;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    if (value.length < 4) {
      alert("Name is too short.");
      return;
    }
    this._name = value;
  }

}

let user = new User("John");
alert(user.name); // John

user = new User(""); // le nom est trop court.

Techniquement, une telle déclaration de classe fonctionne en créant des getters et des setters dans User.prototype.

Computed names [
]

Voici un exemple avec un nom de méthode calculé utilisant des crochets [...] :

class User {

  ['say' + 'Hi']() {
    alert("Hello");
  }

}

new User().sayHi();

Ces caractĂ©ristiques sont faciles Ă  retenir, car elles ressemblent Ă  celles d’objets littĂ©raux.

Champs de classe

Les anciens navigateurs peuvent avoir besoin de polyfill

Les propriétés de classe sont un ajout récent au langage.

Auparavant, nos classes n’avaient que des mĂ©thodes.

Les “Class fields” (champs de classe) sont une syntaxe qui permet d’ajouter des propriĂ©tĂ©s.

Par exemple, ajoutons la propriété name à class User :

class User {
  name = "John";

  sayHi() {
    alert(`Hello, ${this.name}!`);
  }
}

new User().sayHi(); // Hello, John!

Il suffit donc d’écrire “ = ” dans la dĂ©claration, et c’est tout.

La diffĂ©rence importante des champs de classe est qu’ils sont dĂ©finis sur des objets individuels, et non sur User.prototype :

class User {
  name = "John";
}

let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined

Nous pouvons Ă©galement attribuer des valeurs Ă  l’aide d’expressions et d’appels de fonctions plus complexes :

class User {
  name = prompt("Name, please?", "John");
}

let user = new User();
alert(user.name); // John

Création de méthodes liées avec des champs de classe

Comme dĂ©montrĂ© dans le chapitre Le "bind" de fonction les fonctions en JavaScript ont un this dynamique. Cela dĂ©pend du contexte de l’appel.

Donc, si une méthode objet est contournée et appelée dans un autre contexte, this ne sera plus une référence à son objet.

Par exemple, ce code affichera undefined :

class Button {
  constructor(value) {
    this.value = value;
  }

  click() {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // undefined

Le problĂšme est appelĂ© “perdre le this”.

Il existe deux approches pour le corriger, comme indiqué dans le chapitre Le "bind" de fonction :

  1. Passer une fonction wrapper, telle que setTimeout(() => button.click(), 1000).
  2. Lier la mĂ©thode Ă  l’objet, par exemple dans le constructeur.

Les champs de classe fournissent une syntaxe plus élégante :

class Button {
  constructor(value) {
    this.value = value;
  }
  click = () => {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // hello

Le champ de classe click = () => {...} est créé par objet, il y a une fonction distincte pour chaque objet Button, avec this Ă  l’intĂ©rieur rĂ©fĂ©rençant cet objet. Nous pouvons passer button.click n’importe oĂč, et la valeur de this sera toujours correcte.

C’est particuliĂšrement utile dans un environnement de navigateur, pour les Ă©couteurs d’évĂ©nements.

Résumé

La syntaxe de base d’une classe ressemble à ceci :

class MyClass {
  prop = value; // propriété

  constructor(...) { // constructeur
    // ...
  }

  method(...) {} // méthode

  get something(...) {} //  méthode définie avec un accesseur
  set something(...) {} //  méthode définie avec un mutateur

  [Symbol.iterator]() {} // méthode avec un nom évalué (symbole ici)
  // ...
}

MyClass est techniquement une fonction (celle que nous fournissons en tant que constructor), tandis que les méthodes, accesseurs et mutateurs sont écrits dans MyClass.prototype.

Dans les prochains chapitres nous apprendrons plus Ă  propos des classes, y compris la notion d’hĂ©ritage et les autres caractĂ©ristiques.

Exercices

importance: 5

La classe Clock (voir la sandbox) est Ă©crite en style fonctionnelle. Réécrivez la en syntaxe de “classe”.

P.S. La montre doit tictaquer dans la console, ouvrez la pour la voir.

Open a sandbox for the task.

class Clock {
  constructor({ template }) {
    this.template = template;
  }

  render() {
    let date = new Date();

    let hours = date.getHours();
    if (hours < 10) hours = '0' + hours;

    let mins = date.getMinutes();
    if (mins < 10) mins = '0' + mins;

    let secs = date.getSeconds();
    if (secs < 10) secs = '0' + secs;

    let output = this.template
      .replace('h', hours)
      .replace('m', mins)
      .replace('s', secs);

    console.log(output);
  }

  stop() {
    clearInterval(this.timer);
  }

  start() {
    this.render();
    this.timer = setInterval(() => this.render(), 1000);
  }
}


let clock = new Clock({template: 'h:m:s'});
clock.start();

Ouvrez la solution dans une sandbox.

Carte du tutoriel

Commentaires

lire ceci avant de commenter

  • Si vous avez des amĂ©liorations Ă  suggĂ©rer, merci de soumettre une issue GitHub ou une pull request au lieu de commenter.
  • Si vous ne comprenez pas quelque chose dans l'article, merci de prĂ©ciser.
  • Pour insĂ©rer quelques bouts de code, utilisez la balise <code>, pour plusieurs lignes – enveloppez-les avec la balise <pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen
)