En JavaScript, nous ne pouvons hériter que dâun seul objet. Il ne peut y avoir quâun [[Prototype]] pour un objet. Et une classe peut étendre quâune seule autre classe.
Mais parfois, cela semble limitant. Par exemple, nous avons une classe StreetSweeper et une classe Bicycle, et nous voulons faire leur mélange : un StreetSweepingBicycle.
Ou nous avons une classe User et une classe EventEmitter qui implémente la génération dâévénements, et nous aimerions ajouter la fonctionnalité de EventEmitter à User afin que nos utilisateurs puissent émettre des événements.
Il existe un concept qui peut aider ici, appelé âmixinsâ.
Comme défini dans Wikipedia, un mixin est une classe contenant des méthodes qui peuvent être utilisées par dâautres classes sans avoir à en hériter.
En dâautres termes, un mixin fournit des méthodes qui implémentent un certain comportement, mais nous ne lâutilisons pas seul, nous lâutilisons pour ajouter le comportement à dâautres classes.
Un exemple de mixin
Le moyen le plus simple dâimplémenter un mixin en JavaScript est de créer un objet avec des méthodes utiles, de sorte que nous puissions facilement les fusionner dans un prototype de nâimporte quelle classe.
Par exemple ici, le mixin sayHiMixin est utilisé pour ajouter un peu de âdiscoursâ à User :
// mixin
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
}
};
// usage:
class User {
constructor(name) {
this.name = name;
}
}
// copier les méthodes
Object.assign(User.prototype, sayHiMixin);
// maintenant User peut dire bonjour
new User("Dude").sayHi(); // Hello Dude!
Il nây a pas dâhéritage, mais une simple copie de méthode. Ainsi, User peut hériter dâune autre classe et inclure le mixin pour ajouter les méthodes supplémentaires, comme ceci :
class User extends Person {
// ...
}
Object.assign(User.prototype, sayHiMixin);
Les mixins peuvent utiliser lâhéritage à lâintérieur dâeux-mêmes.
Par exemple, ici sayHiMixin hérite de sayMixin :
let sayMixin = {
say(phrase) {
alert(phrase);
}
};
let sayHiMixin = {
__proto__: sayMixin, // (ou nous pourrions utiliser Object.setPrototypeOf pour définir le prototype ici)
sayHi() {
// appeler la méthode du parent
super.say(`Hello ${this.name}`); // (*)
},
sayBye() {
super.say(`Bye ${this.name}`); // (*)
}
};
class User {
constructor(name) {
this.name = name;
}
}
// copier les méthodes
Object.assign(User.prototype, sayHiMixin);
// maintenant User peut dire bonjour
new User("Dude").sayHi(); // Hello Dude!
Veuillez noter que lâappel à la méthode du parent super.say() à partir de sayHiMixin (aux lignes étiquetées avec (*)) recherche la méthode dans le prototype de ce mixin, pas la classe.
Voici le schéma (voir la partie droite) :
Câest parce que les méthodes sayHi et sayBye ont été initialement créées dans sayHiMixin. Ainsi, même si elles ont été copiées, leur propriété interne [[HomeObject]] fait référence à sayHiMixin, comme indiqué sur lâimage ci-dessus.
Comme super cherche des méthodes du parent dans [[HomeObject]].[[Prototype]], cela signifie quâil cherche sayHiMixin.[[Prototype]].
EventMixin
Faisons maintenant un mixin concret.
Une caractéristique importante de nombreux objets de navigateur (par exemple) est quâils peuvent générer des événements. Les événements sont un excellent moyen de âdiffuser des informationsâ à tous ceux qui le souhaitent. Faisons donc un mixin qui permet dâajouter facilement des fonctions relatives aux événements à nâimporte quelle classe/objet.
- Le mixin fournira une méthode
.trigger(name, [... data])pour âgénérer un événementâ quand quelque chose dâimportant lui arrive. Lâargumentnameest un nom de lâévénement, éventuellement suivi dâarguments supplémentaires avec les données dâévénement. - Ãgalement la méthode
.on(name, handler)qui ajoute la fonctionhandleren tant quâécouteur aux événements portant le nom donné. Il sera appelé lorsquâun événement avec lenamedonné se déclenche, et récupérera les arguments de lâappel.trigger. - ⦠Et la méthode
.off(name, handler)qui supprime le programme dâécoutehandler.
Après avoir ajouté le mixin, un objet user sera capable de générer un événement "login" lorsque le visiteur se connectera. Un autre objet, par exemple, calendar peut vouloir écouter de tels événements pour charger le calendrier de la personne connectée.
Ou bien, un menu peut générer lâévénement "select" lorsquâun élément de menu est sélectionné, et dâautres objets peuvent affecter des gestionnaires pour réagir à cet événement. Etc.
Voici le code :
let eventMixin = {
/**
* Souscrire à l'événement, usage :
* menu.on('select', function(item) { ... }
*/
on(eventName, handler) {
if (!this._eventHandlers) this._eventHandlers = {};
if (!this._eventHandlers[eventName]) {
this._eventHandlers[eventName] = [];
}
this._eventHandlers[eventName].push(handler);
},
/**
* Annuler la souscription, usage :
* menu.off('select', handler)
*/
off(eventName, handler) {
let handlers = this._eventHandlers?.[eventName];
if (!handlers) return;
for (let i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
handlers.splice(i--, 1);
}
}
},
/**
* Générer un événement avec le nom et les données donnés
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
if (!this._eventHandlers?.[eventName]) {
return; // aucun gestionnaire pour ce nom d'événement
}
// appeler les gestionnaires
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
}
};
.on(eventName, handler)â assigne la fonctionhandlerà exécuter lorsque lâévénement portant ce nom se produit. Techniquement, il existe une propriété_eventHandlers, qui stocke un tableau de gestionnaires pour chaque nom dâévénement, et il lâajoute simplement à la liste..off(eventName, handler)â supprime la fonction de la liste des gestionnaires..trigger(eventName, ... args)â génère lâévénement : tous les gestionnaires de_eventHandlers[eventName]sont appelés, avec une liste dâarguments...args.
Usage :
// Créez une classe
class Menu {
choose(value) {
this.trigger("select", value);
}
}
// Ajouter le mixin avec les méthodes liées aux événements
Object.assign(Menu.prototype, eventMixin);
let menu = new Menu();
// ajouter un gestionnaire, à appeler lors de la sélection :
menu.on("select", value => alert(`Value selected: ${value}`));
// déclenche l'événement => le gestionnaire ci-dessus s'exécute et affiche :
// Value selected: 123
menu.choose("123");
Maintenant, si nous souhaitons que le code réagisse lors de la sélection du menu, nous pouvons lâécouter avec menu.on(...).
Et le mixin eventMixin facilite lâajout dâun tel comportement à autant de classes que nous le voudrions, sans interférer avec la chaîne dâhéritage.
Résumé
Mixin est un terme générique de programmation orienté objet : une classe contenant des méthodes pour dâautres classes.
Dâautres langages autorisent lâhéritage multiple. JavaScript ne prend pas en charge lâhéritage multiple, mais les mixins peuvent être implémentés en copiant les méthodes dans le prototype.
Nous pouvons utiliser les mixins comme moyen dâajouter à une classe plusieurs comportements, comme la gestion dâévénements, comme nous lâavons vu ci-dessus.
Les mixins peuvent devenir un point de conflit sâils écrasent accidentellement les méthodes de classe existantes. En règle générale, il convient de bien réfléchir aux méthodes de nommage dâun mixin, afin de minimiser la probabilité que cela se produise.

Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâ¦)