Comprendre les prototypes et l’héritage en JavaScript

introduction

JavaScript est un * langage basé sur un prototype *, ce qui signifie que les propriétés et les méthodes d’objet peuvent être partagées via des objets généralisés pouvant être clonés et étendus. Cet héritage est connu sous le nom d’héritage prototype et diffère de l’héritage de classe. Parmi les langages de programmation orientés objet populaires, JavaScript est relativement unique, car d’autres langages importants tels que PHP, Python et Java sont des langages basés sur des classes, qui définissent plutôt des classes en tant que modèles d’objets.

Dans ce didacticiel, nous allons apprendre ce que sont les prototypes d’objets et comment utiliser la fonction constructeur pour étendre les prototypes à de nouveaux objets. Nous étudierons également l’héritage et la chaîne de prototypes.

Prototypes JavaScript

Dans Comprendre des objets en JavaScript, nous avons examiné le type de données de l’objet, comment créer un objet et comment accéder à un objet et le modifier. Propriétés. Nous allons maintenant apprendre comment utiliser des prototypes pour étendre des objets.

Chaque objet en JavaScript a une propriété interne appelée + +. Nous pouvons le démontrer en créant un nouvel objet vide.

let x = {};

C’est ainsi que nous créerions normalement un objet, mais notons qu’une autre façon de le faire consiste à utiliser le constructeur d’objet: + let x = new Object () +.

Pour trouver le + + de cet objet nouvellement créé, nous allons utiliser la méthode + getPrototypeOf () +.

Object.getPrototypeOf(x);

La sortie comportera plusieurs propriétés et méthodes intégrées.

Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

Une autre façon de trouver le prototype [] [] consiste à utiliser la propriété + proto +. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto [+ proto +] est une propriété qui expose le `+ + + interne d’un objet.

x.__proto__;

Le résultat sera le même que si vous aviez utilisé + getPrototypeOf () +.

Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

Il est important que chaque objet en JavaScript ait un + + car il crée un moyen de lier deux ou plusieurs objets.

Les objets que vous créez ont un + +, comme les objets intégrés, tels que + Date et` + Array I`. Il est possible de faire référence à cette propriété interne d’un objet à un autre via la propriété + prototype +, comme nous le verrons plus loin dans ce tutoriel.

Héritage prototype

Lorsque vous essayez d’accéder à une propriété ou à une méthode d’un objet, JavaScript commence par rechercher l’objet lui-même. S’il ne le trouve pas, il recherchera le + + de l’objet. Si, après consultation de l’objet et de son + + toujours, aucune correspondance n’est trouvée, JavaScript vérifie le prototype de l’objet lié et poursuit la recherche jusqu’à la fin de la chaîne de prototypes.

Https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype [+ Object.prototype +] se trouve à la fin de la chaîne de prototypes. Tous les objets héritent des propriétés et méthodes de https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object [+ Object +]. Toute tentative de recherche au-delà de la fin de la chaîne se traduit par + null +.

Dans notre exemple, + x + est un objet vide qui hérite de + Object +. + x + peut utiliser n’importe quelle propriété ou méthode que + Object + a, comme + toString () +.

x.toString();
Output[object Object]

Cette chaîne de prototypes n’est qu’un seul maillon. + x ++ Object +. Nous le savons, car si nous essayons d’enchaîner deux propriétés + +, ce sera + null +.

x.__proto__.__proto__;
Outputnull

Regardons un autre type d’objet. Si vous avez une expérience Working avec des tableaux en JavaScript, vous savez qu’ils ont de nombreuses méthodes intégrées, telles que + pop () + `et + push () + . La raison pour laquelle vous avez accès à ces méthodes lorsque vous créez un nouveau tableau est parce que tout tableau que vous créez a accès aux propriétés et aux méthodes sur le `+ Array.prototype +.

Nous pouvons tester cela en créant un nouveau tableau.

let y = [];

Gardez à l’esprit que nous pourrions également l’écrire en tant que constructeur de tableau, + let y = new Array () +.

Si nous jetons un coup d’œil au + + du nouveau tableau + y +, nous verrons qu’il a plus de propriétés et de méthodes que l’objet + x +. Il a tout hérité de + Array.prototype +.

y.__proto__;
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]

Vous remarquerez une propriété + constructeur + sur le prototype définie sur + Array () +. La propriété + constructor + renvoie la fonction constructeur d’un objet, mécanisme utilisé pour construire des objets à partir de fonctions.

Nous pouvons maintenant relier deux prototypes, car notre chaîne de prototypes est plus longue dans ce cas. Il ressemble à + ​​y ++ + Array →` + Object N`.

y.__proto__.__proto__;
Output{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

Cette chaîne fait maintenant référence à + ​​Object.prototype +. Nous pouvons tester le + + interne contre la propriété + prototype + de la fonction constructeur pour voir qu’ils font référence à la même chose.

y.__proto__ === Array.prototype;            // true
y.__proto__.__proto__ === Object.prototype; // true

Nous pouvons également utiliser la méthode + isPrototypeOf () + pour accomplir cela.

Array.prototype.isPrototypeOf(y);      // true
Object.prototype.isPrototypeOf(Array); // true

Nous pouvons utiliser l’opérateur + instanceof + pour vérifier si la propriété + prototype + d’un constructeur apparaît n’importe où dans la chaîne de prototypes d’un objet.

y instanceof Array; // true

Pour résumer, tous les objets JavaScript ont une propriété interne masquée + + (qui peut être exposée via + proto + dans certains navigateurs). Les objets peuvent être étendus et hériteront des propriétés et des méthodes sur + + de leur constructeur.

Ces prototypes peuvent être chaînés et chaque objet supplémentaire héritera de tout au long de la chaîne. La chaîne se termine par le + Object.prototype +.

Fonctions du constructeur

  • Les fonctions constructeur * sont des fonctions utilisées pour construire de nouveaux objets. Le https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new [+ new + operator] est utilisé pour créer de nouvelles instances basées sur une fonction constructeur. Nous avons vu certains constructeurs JavaScript intégrés, tels que + new Array () + et + new Date () +, mais nous pouvons également créer nos propres modèles personnalisés à partir desquels créer de nouveaux objets.

Par exemple, imaginons que nous créons un jeu de rôle très simple, basé sur du texte. Un utilisateur peut sélectionner un personnage, puis choisir sa classe de personnage (guerrier, guérisseur, voleur, etc.).

Étant donné que chaque personnage partage de nombreuses caractéristiques, telles qu’un nom, un niveau et des points de repère, il est logique de créer un constructeur en tant que modèle. Cependant, étant donné que chaque classe de personnage peut avoir des capacités très différentes, nous voulons nous assurer que chaque personnage n’a accès qu’à ses propres capacités. Voyons comment y parvenir avec l’héritage et les constructeurs de prototypes.

Pour commencer, une fonction constructeur est juste une fonction régulière. Il devient un constructeur lorsqu’il est appelé par une instance avec le mot clé + new +. En JavaScript, nous capitalisons la première lettre d’une fonction constructeur par convention.

characterSelect.js

// Initialize a constructor function for a new Hero
function Hero(name, level) {
 this.name = name;
 this.level = level;
}

Nous avons créé une fonction constructeur appelée + Hero + avec deux paramètres: + name + et + level +. Comme chaque personnage aura un nom et un niveau, il est logique que chaque nouveau personnage ait ces propriétés. Le mot-clé + this + fait référence à la nouvelle instance créée. Par conséquent, définir le paramètre + this.name + sur le paramètre + name + garantit que le nouvel objet aura une propriété + name +.

Nous pouvons maintenant créer une nouvelle instance avec + new +.

let hero1 = new Hero('Bjorn', 1);

Si nous consolons + hero1 +, nous verrons qu’un nouvel objet a été créé avec les nouvelles propriétés définies comme prévu.

OutputHero {name: "Bjorn", level: 1}

Maintenant, si nous obtenons le + + de + hero1 +, nous pourrons voir le + constructeur + comme + + Hero () +. (Rappelez-vous que ceci a la même entrée que + hero1 . proto +, mais c’est la méthode appropriée à utiliser.)

Object.getPrototypeOf(hero1);
Outputconstructor: ƒ Hero(name, level)

Vous remarquerez peut-être que nous ne définissons que des propriétés et non des méthodes dans le constructeur. En JavaScript, il est courant de définir des méthodes sur le prototype pour améliorer l’efficacité et la lisibilité du code.

Nous pouvons ajouter une méthode à + ​​Hero + en utilisant + prototype +. Nous allons créer une méthode + greet () +.

characterSelect.js

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

Puisque + greet () + est dans le + prototype + de + Hero +, et que '+ hero1 + est une instance de + Hero + , la méthode est disponible pour + hero1 + `.

hero1.greet();
Output"Bjorn says hello."

Si vous inspectez le + + de Hero, vous verrez maintenant + greet () + comme option disponible.

C’est bien, mais nous souhaitons maintenant créer des classes de personnages que les héros pourront utiliser. Cela n’aurait aucun sens de mettre toutes les capacités de chaque classe dans le constructeur + Hero + ', car différentes classes auront des capacités différentes. Nous voulons créer de nouvelles fonctions constructeur, mais nous voulons aussi qu’elles soient connectées au `+ Hero + d’origine.

Nous pouvons utiliser la méthode https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call [+ call () +] pour copier les propriétés d’un constructeur dans un autre constructeur. Créons un constructeur Warrior et Healer.

characterSelect.js

...
// Initialize Warrior constructor
function Warrior(name, level, weapon) {
 // Chain constructor with call
 Hero.call(this, name, level);

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

// Initialize Healer constructor
function Healer(name, level, spell) {
 Hero.call(this, name, level);

 this.spell = spell;
}

Les deux nouveaux constructeurs ont maintenant les propriétés + Hero et quelques unes uniques. Nous allons ajouter la méthode + attack () + à + ​​Warrior +, et la méthode + heal () + à + ​​+ Healer +.

characterSelect.js

...
Warrior.prototype.attack = function () {
 return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () {
 return `${this.name} casts ${this.spell}.`;
}

À ce stade, nous allons créer nos personnages avec les deux nouvelles classes de personnages disponibles.

characterSelect.js

const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

+ hero1 + est maintenant reconnu en tant que + Warrior + avec les nouvelles propriétés.

OutputWarrior {name: "Bjorn", level: 1, weapon: "axe"}

Nous pouvons utiliser les nouvelles méthodes que nous avons définies sur le prototype + Warrior +.

hero1.attack();
Console"Bjorn attacks with the axe."

Mais que se passe-t-il si nous essayons d’utiliser des méthodes plus loin dans la chaîne de prototypes?

hero1.greet();
OutputUncaught TypeError: hero1.greet is not a function

Les propriétés et les méthodes du prototype ne sont pas automatiquement liées lorsque vous utilisez + call () + pour chaîner les constructeurs. Nous allons utiliser + Object.create () + pour lier les prototypes, en veillant à le mettre avant que toute méthode supplémentaire ne soit créée et ajoutée au prototype.

characterSelect.js

...
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);

// All other prototype methods added below
...

Nous pouvons maintenant utiliser avec succès les méthodes prototypes de + Hero + sur une instance de` + Warrior` ou + Healer.

hero1.greet();
Output"Bjorn says hello."

Voici le code complet pour notre page de création de personnage.

characterSelect.js

// Initialize constructor functions
function Hero(name, level) {
 this.name = name;
 this.level = level;
}

function Warrior(name, level, weapon) {
 Hero.call(this, name, level);

 this.weapon = weapon;
}

function Healer(name, level, spell) {
 Hero.call(this, name, level);

 this.spell = spell;
}

// Link prototypes and add prototype methods
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);

Hero.prototype.greet = function () {
 return `${this.name} says hello.`;
}

Warrior.prototype.attack = function () {
 return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () {
 return `${this.name} casts ${this.spell}.`;
}

// Initialize individual character instances
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

Avec ce code, nous avons créé la classe + Hero + avec les propriétés de base, créé deux classes de caractères nommées + Warrior + et + Healer +, ajouté des méthodes aux prototypes et créé des occurrences individuelles.

Conclusion

JavaScript est un langage basé sur un prototype et fonctionne différemment du paradigme traditionnel basé sur les classes utilisé par de nombreux autres langages orientés objet.

Dans ce didacticiel, nous avons appris comment les prototypes fonctionnent en JavaScript et comment lier les propriétés et les méthodes des objets via la propriété masquée + + partagée par tous les objets. Nous avons également appris à créer des fonctions de constructeur personnalisées et à comprendre comment fonctionne l’héritage de prototype pour transmettre les valeurs de propriété et de méthode.