Comprendre les classes en JavaScript

PostedFebruary 26, 2020 1.4k views DevelopmentJavaScript

Introduction

JavaScript est un langage basé sur des prototypes, et chaque objet en JavaScript possède une propriété interne cachée appelée [[Prototype]] qui peut être utilisée pour étendre les propriétés et les méthodes des objets. Vous pouvez en savoir plus sur les prototypes dans notre tutoriel Comprendre les prototypes et l'héritage en JavaScript.

Jusqu'à récemment, les développeurs industriels utilisaient des fonctions de constructeur pour imiter un modèle de conception orienté objet en JavaScript. La spécification de langage ECMAScript 2015, souvent appelée ES6, a introduit des classes dans le langage JavaScript. Les classes en JavaScript n'offrent pas réellement de fonctionnalités supplémentaires, et sont souvent décrites comme fournissant du « sucre syntaxique » par rapport aux prototypes et à l'héritage, en ce sens qu'elles offrent une syntaxe plus propre et plus élégante. Comme d'autres langages de programmation utilisent des classes, la syntaxe des classes en JavaScript permet aux développeurs de passer plus facilement d'un langage à l'autre.

Les classes sont des fonctions

Une classe JavaScript est un type de fonction. Les classes sont déclarées avec le mot-clé class. Nous utiliserons la syntaxe d'expression de fonction pour initialiser une fonction et la syntaxe d'expression de classe pour initialiser une classe.

// Initializing a function with a function expression
const x = function() {}
// Initializing a class with a class expression
const y = class {}

Nous pouvons accéder au [[Prototype]] d'un objet en utilisant la méthode Object.getPrototypeOf(). Utilisons cela pour tester la fonction vide que nous avons créée.

Object.getPrototypeOf(x);
Output
ƒ () { [native code] }

Nous pouvons également utiliser cette méthode sur la classe que nous venons de créer.

Object.getPrototypeOf(y);
Output
ƒ () { [native code] }

Les codes déclarés avec function et class renvoient tous deux une fonction [[Prototype]]. Avec les prototypes, toute fonction peut devenir une instance de constructeur en utilisant le mot-clé new.

const x = function() {}

// Initialize a constructor from a function
const constructorFromFunction = new x();

console.log(constructorFromFunction);
Output
x {} constructor: ƒ ()

Cela s'applique également aux classes.

const y = class {}

// Initialize a constructor from a class
const constructorFromClass = new y();

console.log(constructorFromClass);
Output
y {} constructor: class

Ces exemples de constructeurs de prototypes sont par ailleurs vides, mais nous pouvons voir comment, au-delà de la syntaxe, les deux méthodes aboutissent au même résultat final.

Définir une classe

Dans le tutoriel sur les prototypes et l'héritage, nous avons créé un exemple basé sur la création de personnages dans un jeu de rôle en mode texte. Continuons avec cet exemple pour modifier la syntaxe en la faisant passer des fonctions aux classes.

Une fonction de constructeur est initialisée avec un certain nombre de paramètres, qui seraient attribués comme propriétés de this, en référence à la fonction elle-même. La première lettre de l'identifiant serait en majuscule par convention.

constructor.js
// Initializing a constructor function
function Hero(name, level) {
    this.name = name;
    this.level = level;
}

Lorsque nous traduisons cela dans la syntaxe de classe, illustrée ci-dessous, nous constatons qu'elle est structurée de manière très similaire.

class.js
// Initializing a class definition
class Hero {
    constructor(name, level) {
        this.name = name;
        this.level = level;
    }
}

Nous savons qu'une fonction de constructeur est censée être un plan d'objet par la capitalisation de la première lettre de l'initialiseur (qui est facultative) et par la familiarité avec la syntaxe. Le mot-clé class communique de manière plus directe l'objectif de notre fonction.

La seule différence dans la syntaxe de l'initialisation est l'utilisation du mot-clé class au lieu de function, et l'attribution des propriétés à l'intérieur d'une méthode constructor ().

Définir les méthodes

La pratique courante avec les fonctions de constructeur est d'assigner les méthodes directement au prototype plutôt que dans l'initialisation, comme on le voit dans la méthode greet() ci-dessous.

constructor.js
function Hero(name, level) {
    this.name = name;
    this.level = level;
}

// Adding a method to the constructor
Hero.prototype.greet = function() {
    return `${this.name} says hello.`;
}

Avec les classes, cette syntaxe est simplifiée, et la méthode peut être ajoutée directement à la classe. En utilisant la syntaxe simplifiée de définition de méthode introduite dans l'ES6, la définition d'une méthode est un processus encore plus concis.

class.js
class Hero {
    constructor(name, level) {
        this.name = name;
        this.level = level;
    }

    // Adding a method to the constructor
    greet() {
        return `${this.name} says hello.`;
    }
}

Examinons ces propriétés et ces méthodes en action. Nous allons créer une nouvelle instance de Hero en utilisant le mot-clé new, et lui attribuer quelques valeurs.

const hero1 = new Hero('Varg', 1);

Si nous imprimons plus d'informations sur notre nouvel objet avec console.log(hero1), nous pouvons voir plus de détails sur ce qui se passe avec l'initialisation de la classe.

Output
Hero {name: "Varg", level: 1} __proto__: ▶ constructor: class Hero ▶ greet: ƒ greet()

Nous pouvons voir dans la sortie que les fonctions constructor() et greet() ont été appliquées au __proto__, ou [[Prototype]] de hero1, et non directement comme méthode sur l'objet hero1. Si cela est évident lors de la création de fonctions de constructeur, ce n'est pas le cas lors de la création de classes. Les classes permettent une syntaxe plus simple et plus succincte, mais sacrifient une certaine clarté dans le processus.

Étendre une classe

L'une des caractéristiques avantageuses des fonctions de constructeur et des classes est qu'elles peuvent être étendues à de nouveaux plans d'objet basés sur le parent. Cela permet d'éviter la répétition du code pour des objets qui sont similaires mais qui nécessitent des caractéristiques supplémentaires ou plus spécifiques.

De nouvelles fonctions de constructeur peuvent être créées à partir du parent en utilisant la méthode call(). Dans l'exemple ci-dessous, nous allons créer une classe de personnage plus spécifique appelée Mage, et lui attribuer les propriétés de Hero en utilisant call(), tout en ajoutant une propriété supplémentaire.

constructor.js
// Creating a new constructor from the parent
function Mage(name, level, spell) {
    // Chain constructor with call
    Hero.call(this, name, level);

    this.spell = spell;
}

À ce stade, nous pouvons créer une nouvelle instance de Mage en utilisant les mêmes propriétés que celles de Hero, ainsi qu'une nouvelle propriété que nous avons ajoutée.

const hero2 = new Mage('Lejon', 2, 'Magic Missile');

En envoyant hero2 à la console, nous pouvons voir que nous avons créé un nouveau Mage basé sur le constructeur.

Output
Mage {name: "Lejon", level: 2, spell: "Magic Missile"} __proto__: ▶ constructor: ƒ Mage(name, level, spell)

Avec les classes ES6, le mot-clé super est utilisé à la place de call pour accéder aux fonctions parents. Nous utiliserons extends pour renvoyer à la classe parent.

class.js
// Creating a new class from the parent
class Mage extends Hero {
    constructor(name, level, spell) {
        // Chain constructor with super
        super(name, level);

        // Add a new property
        this.spell = spell;
    }
}

Nous pouvons maintenant créer une nouvelle instance Mage de la même manière.

const hero2 = new Mage('Lejon', 2, 'Magic Missile');

Nous allons imprimer hero2 sur la console et visualiser le résultat.

Output
Mage {name: "Lejon", level: 2, spell: "Magic Missile"} __proto__: Hero ▶ constructor: class Mage

Le résultat est presque exactement le même, sauf que dans la construction de la classe, le [[Prototype]] est lié au parent, dans ce cas Hero.

Vous trouverez ci-dessous une comparaison côte à côte de l'ensemble du processus d'initialisation, d'ajout de méthodes et d'héritage d'une fonction de constructeur et d'une classe.

constructor.js
function Hero(name, level) {
    this.name = name;
    this.level = level;
}

// Adding a method to the constructor
Hero.prototype.greet = function() {
    return `${this.name} says hello.`;
}

// Creating a new constructor from the parent
function Mage(name, level, spell) {
    // Chain constructor with call
    Hero.call(this, name, level);

    this.spell = spell;
}
class.js
// Initializing a class
class Hero {
    constructor(name, level) {
        this.name = name;
        this.level = level;
    }

    // Adding a method to the constructor
    greet() {
        return `${this.name} says hello.`;
    }
}

// Creating a new class from the parent
class Mage extends Hero {
    constructor(name, level, spell) {
        // Chain constructor with super
        super(name, level);

        // Add a new property
        this.spell = spell;
    }
}

Bien que la syntaxe soit assez différente, le résultat sous-jacent est presque le même entre les deux méthodes. Les classes nous donnent un moyen plus concis de créer des plans d'objets, et les fonctions de constructeur décrivent plus précisément ce qui se passe sous le capot.

Conclusion

Dans ce tutoriel, nous avons découvert les similitudes et les différences entre les fonctions de constructeur JavaScript et les classes ES6. Les classes et les constructeurs imitent un modèle d'héritage orienté objet en JavaScript, qui est un langage d'héritage basé sur un prototype.

Comprendre l'héritage des prototypes est primordial pour être un développeur JavaScript efficace. Il est extrêmement utile de se familiariser avec les classes, car les bibliothèques JavaScript populaires telles que React utilisent fréquemment la syntaxe class.

0 Comments

Creative Commons License