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>
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>
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>
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:
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:
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>
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.
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>
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 ...)
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>
<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>
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>
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>
pros
Cons
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>
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>
É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>
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>
Je pense que nous pouvons convenir que ce dernier est plus lisible.
pros
Cons
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>
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>
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>
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>
<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>
pros
Cons
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!
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.
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.
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.
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.
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'.
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.
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.
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.
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.
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!