Maison > interface Web > js tutoriel > Modèles de l'héritage des objets dans JavaScript ES2015

Modèles de l'héritage des objets dans JavaScript ES2015

Lisa Kudrow
Libérer: 2025-02-16 11:38:39
original
820 Les gens l'ont consulté

Modèles de l'héritage des objets dans JavaScript ES2015

Les plats clés

  • Avec ES2015, JavaScript a désormais une syntaxe spécifiquement pour définir des classes afin de garder le code propre, de minimiser la profondeur de la hiérarchie et d'éviter le code en double.
  • L'héritage multiple, une fonctionnalité pris en charge par certaines langues OOP classiques, permet la création d'une classe qui hérite de plusieurs classes de base. Cependant, il souffre du problème du diamant, où deux classes de parents définissent la même méthode.
  • Les mixins, les classes minuscules qui ne contiennent que des méthodes, sont une autre stratégie utilisée pour esquiver le problème du diamant. Au lieu de prolonger ces classes, les mixins sont inclus dans une autre classe.
  • Malgré la syntaxe de classe donnant l'illusion que JavaScript est un langage OOP basé sur la classe, ce n'est pas le cas. La plupart des approches nécessitent la modification du prototype d'un objet pour imiter l'héritage multiple. L'utilisation de fonctions d'usine de classe est une stratégie acceptable pour utiliser des mixins pour composer des classes.
Modèles de l'héritage des objets dans JavaScript ES2015

Avec l'arrivée tant attendue de l'ES2015 (anciennement connu sous le nom d'ES6), JavaScript est équipé d'une syntaxe spécifiquement pour définir des classes. Dans cet article, je vais explorer si nous pouvons tirer parti de la syntaxe de classe pour composer des classes à partir de parties plus petites.

Garder la profondeur de hiérarchie au minimum est important pour garder votre code propre. Être intelligent sur la façon dont vous séparez les cours aide. Pour une grande base de code, une option consiste à créer des classes à partir de pièces plus petites; composition de classes. C'est également une stratégie courante pour éviter le code en double.

Imaginez que nous construisons un jeu où le joueur vit dans un monde d'animaux. Certains sont amis, d'autres sont hostiles (un chien comme moi pourrait dire que tous les chats sont des créatures hostiles). Nous pourrions créer une classe hostileanimal, qui étend l'animal, pour servir de classe de base pour CAT. À un moment donné, nous décidons d'ajouter des robots conçus pour nuire aux humains. La première chose que nous faisons est de créer la classe de robot. Nous avons maintenant deux classes qui ont des propriétés similaires. Hostileanimal et robot sont capables d'attaquer (), par exemple.

Si nous pouvions en quelque sorte définir l'hostilité dans une classe ou un objet séparé, disons hostiles, nous pourrions réutiliser cela pour le chat comme robot. Nous pouvons le faire de diverses manières.

L'héritage multiple est une caractéristique que certains supports classiques des langages OOP. Comme son nom l'indique, il nous donne la capacité de créer une classe qui hérite de plusieurs classes de base. Voyez comment la classe CAT étend plusieurs classes de base dans le code Python suivant:

<span>class Animal(object):
</span>  <span>def walk(self):
</span>    <span># ...
</span>
<span>class Hostile(object):
</span>  <span>def attack(self, target):
</span>    <span># ...
</span>
<span>class Dog(Animal):
</span>  <span># ...
</span>
<span>class Cat(Animal, Hostile):
</span>  <span># ...
</span>
dave <span>= Cat();
</span>dave<span>.walk();
</span>dave<span>.attack(target);
</span>
Copier après la connexion
Copier après la connexion
Copier après la connexion

Une interface est une caractéristique commune dans les langages OOP classiques (typés). Il nous permet de définir les méthodes (et parfois les propriétés) qu'une classe doit contenir. Si cette classe ne le fait pas, le compilateur augmentera une erreur. Le code TypeScript suivant augmenterait une erreur si Cat n'avait pas les méthodes d'attaque () ou de marche ():

<span>class Animal(object):
</span>  <span>def walk(self):
</span>    <span># ...
</span>
<span>class Hostile(object):
</span>  <span>def attack(self, target):
</span>    <span># ...
</span>
<span>class Dog(Animal):
</span>  <span># ...
</span>
<span>class Cat(Animal, Hostile):
</span>  <span># ...
</span>
dave <span>= Cat();
</span>dave<span>.walk();
</span>dave<span>.attack(target);
</span>
Copier après la connexion
Copier après la connexion
Copier après la connexion

L'héritage multiple souffre du problème du diamant (où deux classes de parents définissent la même méthode). Certaines langues esquivent ce problème en mettant en œuvre d'autres stratégies, comme mixins . Les mixins sont de minuscules classes qui ne contiennent que des méthodes. Au lieu de prolonger ces classes, les mixins sont inclus dans une autre classe. En PHP, par exemple, les mixins sont implémentés à l'aide de traits.

<span>interface Hostile {
</span>  <span>attack();
</span><span>}
</span>
<span>class Animal {
</span>  <span>walk();
</span><span>}
</span>
<span>class Dog extends Animal {
</span>  <span>// ...
</span><span>}
</span>
<span>class Cat extends Animal implements Hostile {
</span>  <span>attack() {
</span>    <span>// ...
</span>  <span>}
</span><span>}
</span>
Copier après la connexion
Copier après la connexion

un récapitulatif: syntaxe de classe ES2015

Si vous n'avez pas eu la chance de plonger dans les classes ES2015 ou de sentir que vous n'en connaissez pas assez à leur sujet, assurez-vous de lire JavaScript orienté objet de Jeff Mott - une plongée profonde dans les classes ES6 avant de continuer.

en un mot:

  • class Foo {...} décrit une classe nommée foo
  • class Foo étend que la barre {...} décrit une classe, foo, qui étend une autre classe, bar

Dans le bloc de classe, nous pouvons définir les propriétés de cette classe. Pour cet article, nous n'avons qu'à comprendre les constructeurs et les méthodes:

  • Constructor () {...} est une fonction réservée qui est exécutée lors de la création (new foo ())
  • foo () {...} crée une méthode nommée foo

La syntaxe de classe est principalement du sucre syntaxique par rapport au modèle de prototype de JavaScript. Au lieu de créer une classe, il crée un constructeur de fonction:

<span>class Animal {
</span>  <span>// ...
</span><span>}
</span>
<span>trait Hostile {
</span>  <span>// ...
</span><span>}
</span>
<span>class Dog extends Animal {
</span>  <span>// ...
</span><span>}
</span>
<span>class Cat extends Animal {
</span>  <span>use Hostile;
</span>  <span>// ...
</span><span>}
</span>
<span>class Robot {
</span>  <span>use Hostile;
</span>  <span>// ...
</span><span>}
</span>
Copier après la connexion
Copier après la connexion

Le point à retenir ici est que JavaScript n'est pas un langage OOP basé sur les classes. On pourrait même affirmer que la syntaxe est trompeuse, donnant l'impression qu'il est.

Composer les classes ES2015

Les interfaces

peuvent être imitées en créant une méthode factice qui lance une erreur. Une fois hérité, la fonction doit être remplacée pour éviter l'erreur:

<span>class Foo {}
</span><span>console.log(typeof Foo); // "function"
</span>
Copier après la connexion
Copier après la connexion

Comme suggéré précédemment, cette approche repose sur l'héritage. Pour hériter de plusieurs classes, nous aurons besoin de plusieurs héritage ou de mixins.

Une autre approche serait d'écrire une fonction utilitaire qui valide une classe après sa définition. Un exemple de cela peut être trouvé en attendant un instant, JavaScript prend en charge plusieurs héritage! par Andrea GiamMarchi. Voir la section «Un objet de base.

Il est temps d'explorer diverses façons d'appliquer un héritage et des mixins multiples. Toutes les stratégies examinées ci-dessous sont disponibles sur GitHub.

objet.assign (casclass.prototype, mixin ...)

PRE-ES2015, nous avons utilisé des prototypes d'héritage. Toutes les fonctions ont une propriété prototype. Lors de la création d'une instance à l'aide de New MyFunction (), le prototype est copié sur une propriété dans l'instance. Lorsque vous essayez d'accéder à une propriété qui n'est pas dans le cas, le moteur JavaScript essaiera de le rechercher dans l'objet Prototype.

pour démontrer, jetez un œil au code suivant:

<span>class IAnimal {
</span>  <span>walk() {
</span>    <span>throw new Error('Not implemented');
</span>  <span>}
</span><span>}
</span>
<span>class Dog extends IAnimal {
</span>  <span>// ...
</span><span>}
</span>
<span>const robbie = new Dog();
</span>robbie<span>.walk(); // Throws an error
</span>
Copier après la connexion
Copier après la connexion
Ces objets prototypes peuvent être créés et modifiés au moment de l'exécution. Au départ, j'ai essayé d'utiliser des cours pour animaux et hostiles:

<span>class Animal(object):
</span>  <span>def walk(self):
</span>    <span># ...
</span>
<span>class Hostile(object):
</span>  <span>def attack(self, target):
</span>    <span># ...
</span>
<span>class Dog(Animal):
</span>  <span># ...
</span>
<span>class Cat(Animal, Hostile):
</span>  <span># ...
</span>
dave <span>= Cat();
</span>dave<span>.walk();
</span>dave<span>.attack(target);
</span>
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ce qui précède ne fonctionne pas car les méthodes de classe sont pas énumérables . En pratique, cela signifie object.assign (...) ne copie pas les méthodes à partir des classes. Cela rend également difficile de créer une fonction qui copie les méthodes d'une classe à une autre. Nous pouvons cependant copier chaque méthode manuellement:

<span>interface Hostile {
</span>  <span>attack();
</span><span>}
</span>
<span>class Animal {
</span>  <span>walk();
</span><span>}
</span>
<span>class Dog extends Animal {
</span>  <span>// ...
</span><span>}
</span>
<span>class Cat extends Animal implements Hostile {
</span>  <span>attack() {
</span>    <span>// ...
</span>  <span>}
</span><span>}
</span>
Copier après la connexion
Copier après la connexion

Une autre façon consiste à abandonner les classes et à utiliser des objets comme mixins. Un effet secondaire positif est que les objets de mixin ne peuvent pas être utilisés pour créer des instances, empêchant une mauvaise utilisation.

<span>class Animal {
</span>  <span>// ...
</span><span>}
</span>
<span>trait Hostile {
</span>  <span>// ...
</span><span>}
</span>
<span>class Dog extends Animal {
</span>  <span>// ...
</span><span>}
</span>
<span>class Cat extends Animal {
</span>  <span>use Hostile;
</span>  <span>// ...
</span><span>}
</span>
<span>class Robot {
</span>  <span>use Hostile;
</span>  <span>// ...
</span><span>}
</span>
Copier après la connexion
Copier après la connexion

pros

  • Les mélanges ne peuvent pas être initialisés

Cons

  • nécessite une ligne de code supplémentaire
  • objet.assign () est un peu obscur
  • réinventer l'héritage prototypique pour travailler avec les classes ES2015

Composer des objets dans les constructeurs

avec les classes ES2015, vous pouvez remplacer l'instance en renvoyant un objet dans le constructeur:

<span>class Foo {}
</span><span>console.log(typeof Foo); // "function"
</span>
Copier après la connexion
Copier après la connexion

Nous pouvons tirer parti de cette fonctionnalité pour composer un objet à partir de plusieurs classes à l'intérieur d'une sous-classe. Notez que object.assign (...) ne fonctionne toujours pas bien avec les classes de mixin, j'ai donc utilisé des objets ici également:

<span>class IAnimal {
</span>  <span>walk() {
</span>    <span>throw new Error('Not implemented');
</span>  <span>}
</span><span>}
</span>
<span>class Dog extends IAnimal {
</span>  <span>// ...
</span><span>}
</span>
<span>const robbie = new Dog();
</span>robbie<span>.walk(); // Throws an error
</span>
Copier après la connexion
Copier après la connexion

Étant donné que cela fait référence à une classe (avec des méthodes non énumérables) dans le contexte ci-dessus, object.assign (..., this) ne copie pas les méthodes de CAT. Au lieu de cela, vous devrez définir explicitement des champs et des méthodes à ce sujet pour que Object.assign () puisse les appliquer, comme ainsi:

<span>function <span>MyFunction</span> () {
</span>  <span>this.myOwnProperty = 1;
</span><span>}
</span><span>MyFunction.prototype.myProtoProperty = 2;
</span>
<span>const myInstance = new MyFunction();
</span>
<span>// logs "1"
</span><span>console.log(myInstance.myOwnProperty);
</span><span>// logs "2"
</span><span>console.log(myInstance.myProtoProperty);
</span>
<span>// logs "true", because "myOwnProperty" is a property of "myInstance"
</span><span>console.log(myInstance.hasOwnProperty('myOwnProperty'));
</span><span>// logs "false", because "myProtoProperty" isn’t a property of "myInstance", but "myInstance.__proto__"
</span><span>console.log(myInstance.hasOwnProperty('myProtoProperty'));
</span>
Copier après la connexion

Cette approche n'est pas pratique. Parce que vous retournez un nouvel objet au lieu d'une instance, il est essentiellement équivalent à:

<span>class Animal {
</span>  <span>walk() {
</span>    <span>// ...
</span>  <span>}
</span><span>}
</span>
<span>class Dog {
</span>  <span>// ...
</span><span>}
</span>
<span>Object.assign(Dog.prototype, Animal.prototype);
</span>
Copier après la connexion

Je pense que nous pouvons convenir que ce dernier est plus lisible.

pros

  • ça marche, je suppose?

Cons

  • très obscur
  • zéro bénéfice de la syntaxe de classe ES2015
  • abus des classes ES2015

Fonction d'usine de classe

Cette approche exploite la capacité de JavaScript à définir une classe à l'exécution.

Tout d'abord, nous aurons besoin de classes de base. Dans notre exemple, les animaux et les robots servent de classes de base. Si vous voulez partir de zéro, une classe vide fonctionne aussi.

<span>Object.assign(Cat.prototype, {
</span>  <span>attack: Hostile.prototype.attack,
</span>  <span>walk: Animal.prototype.walk,
</span><span>});
</span>
Copier après la connexion

Ensuite, nous devons créer une fonction d'usine qui renvoie une nouvelle classe qui étend la base de classe, qui est transmise en paramètre. Ce sont les mixins:

<span>const Animal = {
</span>  <span>walk() {
</span>    <span>// ...
</span>  <span>},
</span><span>};
</span>
<span>const Hostile = {
</span>  <span>attack(target) {
</span>    <span>// ...
</span>  <span>},
</span><span>};
</span>
<span>class Cat {
</span>  <span>// ...
</span><span>}
</span>
<span>Object.assign(Cat.prototype, Animal, Hostile);
</span>
Copier après la connexion

Maintenant, nous pouvons transmettre n'importe quelle classe à la fonction hostile qui renverra une nouvelle classe combinant hostile et n'importe quelle classe que nous avons transmise à la fonction:

<span>class Answer {
</span>  <span>constructor(question) {
</span>    <span>return {
</span>      <span>answer: 42,
</span>    <span>};
</span>  <span>}
</span><span>}
</span>
<span>// { answer: 42 }
</span><span>new Answer("Life, the universe, and everything");
</span>
Copier après la connexion

Nous pourrions passer à travers plusieurs classes pour appliquer plusieurs mixins:

<span>const Animal = {
</span>  <span>walk() {
</span>    <span>// ...
</span>  <span>},
</span><span>};
</span>
<span>const Hostile = {
</span>  <span>attack(target) {
</span>    <span>// ...
</span>  <span>},
</span><span>};
</span>
<span>class Cat {
</span>  <span>constructor() {
</span>    <span>// Cat-specific properties and methods go here
</span>    <span>// ...
</span>
    <span>return Object.assign(
</span>      <span>{},
</span>      <span>Animal,
</span>      <span>Hostile,
</span>      <span>this
</span>    <span>);
</span>  <span>}
</span><span>}
</span>
Copier après la connexion
Vous pouvez également utiliser l'objet comme classe de base:
<span>class Cat {
</span>  <span>constructor() {
</span>    <span>this.purr = () => {
</span>      <span>// ...
</span>    <span>};
</span>
    <span>return Object.assign(
</span>      <span>{},
</span>      <span>Animal,
</span>      <span>Hostile,
</span>      <span>this
</span>    <span>);
</span>  <span>}
</span><span>}
</span>
Copier après la connexion

pros

  • Plus facile à comprendre, car toutes les informations sont dans l'en-tête de la déclaration de classe

Cons

  • La création de classes à l'exécution peut avoir un impact sur les performances du démarrage et / ou l'utilisation de la mémoire

Conclusion

Lorsque j'ai décidé de rechercher ce sujet et d'écrire un article à ce sujet, je m'attendais à ce que le modèle prototypique de JavaScript soit utile pour générer des cours. Parce que la syntaxe de la classe rend les méthodes non énumérables, la manipulation des objets devient beaucoup plus difficile, presque peu pratique.

La syntaxe de classe pourrait créer l'illusion que JavaScript est un langage OOP basé sur la classe, mais ce n'est pas le cas. Avec la plupart des approches, vous devrez modifier le prototype d'un objet pour imiter l'héritage multiple. La dernière approche, en utilisant des fonctions d'usine de classe, est une stratégie acceptable pour utiliser des mixins pour composer des classes.

Si vous trouvez une programmation basée sur un prototype restrictif, vous voudrez peut-être regarder votre état d'esprit. Les prototypes offrent une flexibilité inégalée dont vous pouvez profiter.

Si, pour une raison quelconque, vous préférez toujours la programmation classique, vous voudrez peut-être examiner les langages qui compilent à JavaScript. TypeScript, par exemple, est un superset de JavaScript qui ajoute des typages statiques (facultatifs) et des modèles que vous reconnaîtrez à partir d'autres langages OOP classiques.

Allez-vous utiliser l'une ou l'autre des approches ci-dessus dans vos projets? Avez-vous trouvé de meilleures approches? Faites-moi savoir dans les commentaires!

Cet article a été révisé par Jeff Mott, Scott Molinari, Vildan Softic et Joan Yin. Merci à tous les pairs examinateurs de SitePoint pour avoir fait du contenu SitePoint le meilleur possible!

Des questions fréquemment posées sur JavaScript ES2015 Hérédité des objets

Quelle est la différence entre l'héritage classique et prototypique en JavaScript?

L'héritage classique, souvent utilisé dans des langages comme Java et C, est basé sur des classes. Une classe définit un plan pour un objet et les objets sont des instances d'une classe. L'héritage est réalisé en créant des sous-classes à partir d'une superclasse. D'un autre côté, JavaScript utilise l'héritage prototypal où les objets héritent directement des autres objets. Ceci est plus flexible car les objets peuvent être étendus ou modifiés dynamiquement au moment de l'exécution.

Comment fonctionne le mot-clé «super» dans JavaScript ES2015?

Le mot-clé «super» dans JavaScript ES2015 est utilisé pour Fonctions d'appel sur le parent d'un objet. Lorsqu'elle est utilisée dans un constructeur, le mot-clé «super» apparaît seul et doit être utilisé avant que le mot clé «ce» ne soit utilisé. Le mot-clé «super» peut également être utilisé pour appeler les fonctions sur un objet parent dans les méthodes.

Qu'est-ce que le «prototype» en JavaScript et comment est-il utilisé dans l'héritage?

Dans JavaScript, chaque fonction et objet possède une propriété «prototype». Cette propriété est une référence à un autre objet, l'objet prototype. Lorsqu'une fonction est créée, son objet prototype est également créé et lié via la propriété Prototype de la fonction. Lorsqu'un objet est créé à l'aide d'une fonction de constructeur, il hérite des propriétés et des méthodes du prototype de son constructeur.

Comment JavaScript gère-t-il l'héritage multiple?

JavaScript ne prend pas en charge directement l'héritage multiple. Cependant, il peut être réalisé indirectement à l'aide de mixins. Un mélange est une technique qui implique la copie des propriétés d'un objet à l'autre. This allows an object to inherit properties and methods from multiple sources.

What is the 'constructor' in JavaScript ES2015 and how is it used in inheritance?

In JavaScript ES2015, a constructor is a Méthode spéciale utilisée pour créer et initialiser un objet dans une classe. Dans le contexte de l'héritage, un constructeur de sous-classe doit appeler le constructeur Superclass en utilisant le mot clé 'super' avant de pouvoir utiliser le mot-clé 'This'.

Comment le mot-clé 'étend' fonctionne-t-il dans JavaScript ES2015?

Le mot-clé 'étend' dans JavaScript ES2015 est utilisé pour créer une sous-classe à partir d'une superclasse. La sous-classe hérite de toutes les propriétés et méthodes de la superclasse, mais peut également en ajouter de nouvelles ou remplacer celles héritées.

Quelle est la différence entre la «classe» et le «prototype» en javascript?

Dans JavaScript, une «classe» est un type de fonction qui est utilisé comme un plan pour créer des objets. Il résume les données et les fonctions qui fonctionnent sur ces données. D'un autre côté, un «prototype» est un objet à partir duquel d'autres objets héritent des propriétés et des méthodes.

Comment puis-je remplacer une méthode dans une sous-classe dans JavaScript es2015?

dans JavaScript ES2015, Vous pouvez remplacer une méthode dans une sous-classe en définissant simplement une méthode avec le même nom dans la sous-classe. La nouvelle méthode sera utilisée à la place de celle héritée lorsqu'elle est appelée sur une instance de la sous-classe.

Quel est le «nouveau» mot-clé en JavaScript et comment est-il utilisé dans l'héritage?

Le «nouveau» mot-clé dans JavaScript est utilisé pour créer une instance d'une classe ou d'une fonction de constructeur. Dans le contexte de l'héritage, le «nouveau» mot-clé est utilisé pour créer une instance d'une sous-classe qui hérite des propriétés et des méthodes à partir d'une superclasse.

Comment puis-je ajouter une propriété au prototype d'un objet dans JavaScript?

Dans JavaScript, vous pouvez ajouter une propriété au prototype d'un objet en attribuant simplement une valeur à une propriété sur l'objet Prototype. Cette propriété sera alors héritée par tous les objets créés à partir de la fonction du constructeur.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal