In JavaScript können wir nur von einem einzelnen Objekt erben. Für ein Objekt kann es nur einen [[Prototype]]
geben. Und eine Klasse darf nur eine andere Klasse erweitern.
Aber manchmal fühlt sich das einschränkend an. Zum Beispiel haben wir eine Klasse StreetSweeper
und eine Klasse Bicycle
und möchten deren Mischung erstellen: ein StreetSweepingBicycle
.
Oder wir haben eine Klasse User
und eine Klasse EventEmitter
, die die Ereignisgenerierung implementiert, und wir möchten die Funktionalität von EventEmitter
zu User
hinzufügen, damit unsere Benutzer Ereignisse ausgeben können.
Hier hilft ein Konzept namens „Mixins“.
Wie in Wikipedia definiert, ist ein Mixin eine Klasse, die Methoden enthält, die von anderen Klassen verwendet werden können, ohne von ihr erben zu müssen.
Mit anderen Worten: Ein Mixin stellt Methoden bereit, die ein bestimmtes Verhalten implementieren. Wir verwenden es jedoch nicht allein, sondern um das Verhalten anderen Klassen hinzuzufügen.
Der einfachste Weg, ein Mixin in JavaScript zu implementieren, besteht darin, ein Objekt mit nützlichen Methoden zu erstellen, damit wir sie problemlos in einen Prototyp einer beliebigen Klasse zusammenführen können.
Hier wird zum Beispiel das Mixin sayHiMixin
verwendet, um etwas „Sprache“ für User
hinzuzufügen:
// mixin sagen wir HiMixin = { sayHi() { Alert(`Hallo ${this.name}`); }, sayBye() { alarm(`Tschüs ${this.name}`); } }; // Verwendung: Klasse Benutzer { Konstruktor(Name) { this.name = Name; } } // Kopiere die Methoden Object.assign(User.prototype, sayHiMixin); // Jetzt kann der Benutzer Hallo sagen neuer Benutzer("Dude").sayHi(); // Hallo Alter!
Es gibt keine Vererbung, sondern ein einfaches Kopieren der Methode. User
kann also von einer anderen Klasse erben und auch das Mixin einschließen, um die zusätzlichen Methoden einzumischen, etwa so:
Klasse Benutzer erweitert Person { // ... } Object.assign(User.prototype, sayHiMixin);
Mixins können die Vererbung in sich selbst nutzen.
Hier erbt beispielsweise sayHiMixin
von sayMixin
:
sagen wir Mixin = { say(phrase) { Warnung(Satz); } }; sagen wir HiMixin = { __proto__: sayMixin, // (oder wir könnten Object.setPrototypeOf verwenden, um den Prototyp hier festzulegen) sayHi() { // übergeordnete Methode aufrufen super.say(`Hallo ${this.name}`); // (*) }, sayBye() { super.say(`Bye ${this.name}`); // (*) } }; Klasse Benutzer { Konstruktor(Name) { this.name = Name; } } // Kopiere die Methoden Object.assign(User.prototype, sayHiMixin); // Jetzt kann der Benutzer Hallo sagen neuer Benutzer("Dude").sayHi(); // Hallo Alter!
Bitte beachten Sie, dass der Aufruf der übergeordneten Methode super.say()
von sayHiMixin
(in mit (*)
gekennzeichneten Zeilen) nach der Methode im Prototyp dieses Mixins sucht, nicht nach der Klasse.
Hier ist das Diagramm (siehe rechter Teil):
Das liegt daran, dass die Methoden sayHi
und sayBye
ursprünglich in sayHiMixin
erstellt wurden. Obwohl sie also kopiert wurden, verweisen ihre internen Eigenschaftsreferenzen [[HomeObject]]
sayHiMixin
, wie im Bild oben gezeigt.
Da super
nach übergeordneten Methoden in [[HomeObject]].[[Prototype]]
sucht, bedeutet das, dass es sayHiMixin.[[Prototype]]
durchsucht.
Jetzt machen wir einen Mix für das echte Leben.
Ein wichtiges Merkmal vieler Browserobjekte ist beispielsweise, dass sie Ereignisse generieren können. Veranstaltungen sind eine großartige Möglichkeit, Informationen an jeden weiterzugeben, der sie haben möchte. Erstellen wir also ein Mixin, mit dem wir ganz einfach ereignisbezogene Funktionen zu jeder Klasse/jedem Objekt hinzufügen können.
Das Mixin stellt eine Methode .trigger(name, [...data])
bereit, um „ein Ereignis zu generieren“, wenn ihm etwas Wichtiges passiert. Das name
ist ein Name des Ereignisses, optional gefolgt von weiteren Argumenten mit Ereignisdaten.
Außerdem die Methode .on(name, handler)
die handler
als Listener für Ereignisse mit dem angegebenen Namen hinzufügt. Es wird aufgerufen, wenn ein Ereignis mit dem angegebenen name
ausgelöst wird, und ruft die Argumente aus dem .trigger
Aufruf ab.
…Und die Methode .off(name, handler)
die den handler
Listener entfernt.
Nach dem Hinzufügen des Mixins kann ein user
ein Ereignis "login"
generieren, wenn sich der Besucher anmeldet. Und ein anderes Objekt, beispielsweise „ calendar
, möchte möglicherweise auf solche Ereignisse warten, um den Kalender für die angemeldete Person zu laden.
Oder ein menu
kann das Ereignis "select"
generieren, wenn ein Menüelement ausgewählt wird, und andere Objekte können Handler zuweisen, die auf dieses Ereignis reagieren. Und so weiter.
Hier ist der Code:
let eventMixin = { /** * Veranstaltung abonnieren, Nutzung: * 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); }, /** * Abo kündigen, Nutzung: * 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); } } }, /** * Erzeugen Sie ein Ereignis mit dem angegebenen Namen und den angegebenen Daten * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { if (!this._eventHandlers?.[eventName]) { zurückkehren; // keine Handler für diesen Ereignisnamen } // die Handler aufrufen this._eventHandlers[eventName].forEach(handler => handler.apply(this, args)); } };
.on(eventName, handler)
– weist einen handler
zu, der ausgeführt wird, wenn das Ereignis mit diesem Namen auftritt. Technisch gesehen gibt es eine _eventHandlers
Eigenschaft, die ein Array von Handlern für jeden Ereignisnamen speichert und diese einfach zur Liste hinzufügt.
.off(eventName, handler)
– entfernt die Funktion aus der Handlerliste.
.trigger(eventName, ...args)
– generiert das Ereignis: Alle Handler von _eventHandlers[eventName]
werden aufgerufen, mit einer Liste von Argumenten ...args
.
Verwendung:
// Eine Klasse erstellen Klassenmenü { wähle(Wert) { this.trigger("select", value); } } // Das Mixin mit ereignisbezogenen Methoden hinzufügen Object.assign(Menu.prototype, eventMixin); let menu = new Menu(); // einen Handler hinzufügen, der bei Auswahl aufgerufen werden soll: menu.on("select", value => warning(`Ausgewählter Wert: ${value}`)); // löst das Ereignis aus => der obige Handler wird ausgeführt und zeigt: // Ausgewählter Wert: 123 menu.choose("123");
Wenn wir nun möchten, dass Code auf eine Menüauswahl reagiert, können wir mit menu.on(...)
darauf warten.
Und eventMixin
mixin macht es einfach, ein solches Verhalten beliebig vielen Klassen hinzuzufügen, ohne die Vererbungskette zu beeinträchtigen.
Mixin – ist ein allgemeiner objektorientierter Programmierbegriff: eine Klasse, die Methoden für andere Klassen enthält.
Einige andere Sprachen erlauben Mehrfachvererbung. JavaScript unterstützt keine Mehrfachvererbung, aber Mixins können durch Kopieren von Methoden in den Prototyp implementiert werden.
Wir können Mixins verwenden, um eine Klasse zu erweitern, indem wir mehrere Verhaltensweisen hinzufügen, wie z. B. die Ereignisbehandlung, wie wir oben gesehen haben.
Mixins können zu einem Konfliktpunkt werden, wenn sie versehentlich vorhandene Klassenmethoden überschreiben. Daher sollte man im Allgemeinen gut über die Benennungsmethoden eines Mixins nachdenken, um die Wahrscheinlichkeit, dass dies geschieht, zu minimieren.