Comprendre les classes en JavaScript

introduction

JavaScript est un langage basé sur des prototypes et chaque objet en JavaScript possède une propriété interne masqué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 tutorielUnderstanding Prototypes and Inheritance in JavaScript.

Jusqu'à récemment, les développeurs industrieux utilisaientconstructor functions pour imiter un modèle de conception orienté objet en JavaScript. La spécification de langage ECMAScript 2015, souvent appelée ES6, introduit des classes dans le langage JavaScript. Les classes en JavaScript n'offrent pas de fonctionnalités supplémentaires et sont souvent décrites comme fournissant «un sucre syntaxique» par rapport aux prototypes et à l'héritage en ce sens qu'elles offrent une syntaxe plus propre et plus élégante. Etant donné que d'autres langages de programmation utilisent des classes, la syntaxe de classe en JavaScript permet aux développeurs de passer 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 allons utiliser la syntaxe d'expression de fonction pour initialiser une fonction et une 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 {}

On peut accéder aux[[Prototype]] d'un objet en utilisant lesObject.getPrototypeOf() method. Utilisons cela pour tester lesfunction vides que nous avons créés.

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

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

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

Le code déclaré avecfunction etclass renvoient tous deux une fonction[[Prototype]]. Avec les prototypes, toute fonction peut devenir une instance de constructeur à l'aide du mot-clénew.

const x = function() {}

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

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

Cela s'applique aussi aux cours.

const y = class {}

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

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

Ces exemples de constructeur de prototype sont par ailleurs vides, mais nous pouvons voir comment, sous la syntaxe, les deux méthodes permettent d'atteindre le même résultat final.

Définir une classe

Dans lesprototypes and inheritance tutorial, nous avons créé un exemple basé sur la création de personnages dans un jeu de rôle basé sur du texte. Continuons avec cet exemple ici pour mettre à jour la syntaxe des fonctions en classes.

Unconstructor function est initialisé avec un certain nombre de paramètres, qui seraient affectés en tant que propriétés dethis, se référant à la fonction elle-même. La première lettre de l'identifiant serait mise 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 syntaxeclass, illustrée ci-dessous, nous voyons 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 constructeur est censée être un modèle d'objet par la mise en majuscule de la première lettre de l'initialiseur (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 defunction, et l'attribution des propriétés à l'intérieur d'une méthodeconstructor().

Définir des méthodes

La pratique courante avec les fonctions constructeur est d'assigner des méthodes directement auxprototype au lieu de lors de l'initialisation, comme le montre la méthodegreet() 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 lesmethod definition shorthand introduits dans 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.`;
    }
}

Voyons ces propriétés et méthodes en action. Nous allons créer une nouvelle instance deHero en utilisant le mot-clénew et attribuer des valeurs.

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

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

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

Nous pouvons voir dans la sortie que les fonctionsconstructor() etgreet() ont été appliquées aux__proto__, ou[[Prototype]] dehero1, et non directement comme méthode sur l'objethero1. Bien que cela soit clair lors de la création de fonctions de constructeur, ce n'est pas évident lors de la création de classes. Les classes permettent une syntaxe plus simple et succincte, mais sacrifient une certaine clarté dans le processus.

Prolonger une classe

Une fonction avantageuse des fonctions et des classes de constructeur est qu’elles peuvent être étendues à de nouveaux plans d’objet basés sur le parent. Cela empêche la répétition de code pour des objets similaires mais nécessitant des fonctionnalités supplémentaires ou plus spécifiques.

De nouvelles fonctions de constructeur peuvent être créées à partir du parent en utilisant la méthodecall(). Dans l'exemple ci-dessous, nous allons créer une classe de caractères plus spécifique appeléeMage, et lui attribuer les propriétés deHero en utilisantcall(), ainsi qu'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 deMage en utilisant les mêmes propriétés queHero ainsi qu'une nouvelle que nous avons ajoutée.

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

En envoyanthero2 à la console, nous pouvons voir que nous avons créé un nouveauMage basé sur le constructeur.

OutputMage {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 decall pour accéder aux fonctions parentes. Nous utiliseronsextends pour faire référence à la classe parente.

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 deMage de la même manière.

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

Nous imprimeronshero2 sur la console et afficherons la sortie.

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

La sortie est presque exactement la même, sauf que dans la construction de classe,[[Prototype]] est lié au parent, dans ce casHero.

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 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 identique entre les deux méthodes. Les classes nous donnent un moyen plus concis de créer des plans d’objet, et les fonctions de constructeur décrivent plus précisément ce qui se passe sous le capot.

Conclusion

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

Comprendre l'héritage prototypique est primordial pour devenir un développeur JavaScript efficace. Se familiariser avec les classes est extrêmement utile, car les bibliothèques JavaScript populaires telles queReact utilisent fréquemment la syntaxeclass.