Ich bin an einem Punkt angelangt, an dem eine rudimentäre Mehrfachvererbung in JavaScript stattfinden muss. (Ich bin nicht hier, um zu diskutieren, ob dies eine gute Idee ist oder nicht. Bitte behalten Sie diese Kommentare für sich.)
Ich möchte nur wissen, ob jemand dies mit (oder nicht) Erfolg versucht hat und wie er es gemacht hat.
Kochen Sie es auf, was ich wirklich brauchen , um ein Objekt zu können , ist haben fähig ist , eine Eigenschaft von mehr als ein Prototyp Vererbungskette (dh jeder Prototyp könnte seine eigene richtige Kette), aber in einer bestimmten Reihenfolge des Vorrangs (es wird Durchsuchen Sie die Ketten nach der ersten Definition.
Um zu demonstrieren, wie dies theoretisch möglich ist, könnte dies durch Anbringen der Sekundärkette am Ende der Primärkette erreicht werden. Dies würde jedoch alle Instanzen eines dieser vorherigen Prototypen betreffen, und das ist nicht das, was ich möchte.
Gedanken?
Antworten:
Die Mehrfachvererbung kann in ECMAScript 6 mithilfe von Proxy-Objekten erreicht werden .
Implementierung
Erläuterung
Ein Proxy-Objekt besteht aus einem Zielobjekt und einigen Traps, die das benutzerdefinierte Verhalten für grundlegende Operationen definieren.
Wenn wir ein Objekt erstellen, das von einem anderen erbt, verwenden wir
Object.create(obj)
. In diesem Fall möchten wir jedoch eine Mehrfachvererbung. Stattdessen verwendeobj
ich einen Proxy, der grundlegende Operationen auf das entsprechende Objekt umleitet.Ich benutze diese Fallen:
has
Falle ist eine Falle für denin
Bediener . ich benutzesome
überprüfe, ob mindestens ein Prototyp die Eigenschaft enthält.get
Falle ist eine Falle zum Abrufen von Eigenschaftswerten. Ich verwendefind
, um den ersten Prototyp zu finden, der diese Eigenschaft enthält, und gebe den Wert zurück oder rufe den Getter auf dem entsprechenden Empfänger auf. Dies wird von erledigtReflect.get
. Wenn kein Prototyp die Eigenschaft enthält, kehre ich zurückundefined
.set
Falle ist eine Falle zum Festlegen von Eigenschaftswerten. Ich benutzefind
, um den ersten Prototyp zu finden, der diese Eigenschaft enthält, und rufe seinen Setter auf dem entsprechenden Empfänger auf. Wenn kein Setter vorhanden ist oder kein Prototyp die Eigenschaft enthält, wird der Wert auf dem entsprechenden Empfänger definiert. Dies wird von erledigtReflect.set
.enumerate
Falle ist eine Falle fürfor...in
Schleifen . Ich iteriere die aufzählbaren Eigenschaften vom ersten Prototyp, dann vom zweiten und so weiter. Sobald eine Eigenschaft iteriert wurde, speichere ich sie in einer Hash-Tabelle, um eine erneute Iteration zu vermeiden.Warnung : Diese Falle wurde im ES7-Entwurf entfernt und ist in Browsern veraltet.
ownKeys
Falle ist eine Falle fürObject.getOwnPropertyNames()
. Seit ES7 rufenfor...in
Schleifen immer wieder GetPrototypeOf auf und erhalten die eigenen Eigenschaften von jedem. Um die Eigenschaften aller Prototypen zu iterieren, verwende ich diese Falle, um alle aufzählbaren geerbten Eigenschaften als eigene Eigenschaften erscheinen zu lassen.getOwnPropertyDescriptor
Falle ist eine Falle fürObject.getOwnPropertyDescriptor()
. EsownKeys
reicht nicht aus, alle aufzählbaren Eigenschaften als eigene Eigenschaften in der Trap anzuzeigen.for...in
Schleifen erhalten den Deskriptor, um zu überprüfen, ob sie aufzählbar sind. Ich verwende alsofind
, um den ersten Prototyp zu finden, der diese Eigenschaft enthält, und iteriere seine prototypische Kette, bis ich den Eigentümer der Eigenschaft finde, und gebe seinen Deskriptor zurück. Wenn kein Prototyp die Eigenschaft enthält, kehre ich zurückundefined
. Der Deskriptor wurde geändert, um ihn konfigurierbar zu machen, da wir sonst einige Proxy-Invarianten auflösen könnten.preventExtensions
unddefineProperty
Traps sind nur enthalten, um zu verhindern, dass diese Vorgänge das Proxy-Ziel ändern. Andernfalls könnten wir einige Proxy-Invarianten brechen.Es sind weitere Fallen verfügbar, die ich nicht benutze
getPrototypeOf
Falle könnte hinzugefügt werden, aber es gibt keine geeignete Möglichkeit, die mehreren Prototypen zurückzugeben. Dies impliziert, dassinstanceof
es auch nicht funktioniert. Daher lasse ich es den Prototyp des Ziels erhalten, der anfänglich null ist.setPrototypeOf
Falle könnte hinzugefügt werden und eine Reihe von Objekten akzeptieren, die die Prototypen ersetzen würden. Dies bleibt dem Leser als Übung. Hier lasse ich einfach den Prototyp des Ziels modifizieren, was nicht sehr nützlich ist, da keine Falle das Ziel verwendet.deleteProperty
Falle ist eine Falle zum Löschen eigener Eigenschaften. Der Proxy stellt die Vererbung dar, daher wäre dies nicht sehr sinnvoll. Ich lasse es versuchen, das Ziel zu löschen, das sowieso keine Eigenschaft haben sollte.isExtensible
Falle ist eine Falle, um die Erweiterbarkeit zu erhalten. Nicht sehr nützlich, da eine Invariante sie zwingt, dieselbe Erweiterbarkeit wie das Ziel zurückzugeben. Also lasse ich es einfach den Vorgang zum Ziel umleiten, das erweiterbar sein wird.apply
undconstruct
Traps sind Traps zum Aufrufen oder Instanziieren. Sie sind nur nützlich, wenn das Ziel eine Funktion oder ein Konstruktor ist.Beispiel
quelle
multiInherit(o1={a:1}, o2={b:2}, o3={a:3, b:3})
Object.assign
) oder ein ganz anderes Diagramm. Am Ende erhalten alle eine einzige Prototypkette zwischen Objekten. Die Proxy-Lösung bietet eine Laufzeitverzweigung, und das rockt!Update (2019): Der ursprüngliche Beitrag ist ziemlich veraltet. Dieser Artikel (jetzt Internet-Archiv-Link, da die Domain weg ist) und die dazugehörige GitHub-Bibliothek sind ein guter moderner Ansatz.
Ursprünglicher Beitrag: Mehrfachvererbung [Bearbeiten, keine ordnungsgemäße Vererbung des Typs, sondern der Eigenschaften; mixins] in Javascript ist ziemlich einfach, wenn Sie konstruierte Prototypen anstelle von generischen Objekten verwenden. Hier sind zwei übergeordnete Klassen, von denen geerbt werden soll:
Beachten Sie, dass ich in jedem Fall das gleiche "Name" -Mitglied verwendet habe. Dies könnte ein Problem sein, wenn die Eltern nicht einverstanden sind, wie mit "Name" umgegangen werden soll. Aber sie sind in diesem Fall kompatibel (wirklich redundant).
Jetzt brauchen wir nur noch eine Klasse, die von beiden erbt. Die Vererbung erfolgt durch Aufrufen der Konstruktorfunktion (ohne Verwendung des neuen Schlüsselworts) für die Prototypen und die Objektkonstruktoren. Zunächst muss der Prototyp von den übergeordneten Prototypen erben
Und der Konstruktor muss von den übergeordneten Konstruktoren erben:
Jetzt können Sie verschiedene Instanzen anbauen, essen und ernten:
quelle
Array.call(...)
aber es scheint keinen Einfluss darauf zu haben, was ich als passierethis
.Array.prototype.constructor.call()
Dieser verwendet
Object.create
, um eine echte Prototypkette zu erstellen:Beispielsweise:
wird zurückkehren:
so dass
obj.a === 1
,obj.b === 3
usw.quelle
Ich mag John Resigs Implementierung einer Klassenstruktur: http://ejohn.org/blog/simple-javascript-inheritance/
Dies kann einfach erweitert werden auf:
Dadurch können Sie mehrere Objekte übergeben, von denen Sie erben möchten. Sie werden
instanceOf
hier die Fähigkeit verlieren , aber das ist eine Selbstverständlichkeit, wenn Sie mehrere Vererbungen wünschen.Mein ziemlich kompliziertes Beispiel dafür finden Sie unter https://github.com/cwolves/Fetch/blob/master/support/plugins/klass/klass.js
Beachten Sie, dass diese Datei einen toten Code enthält, der jedoch eine Mehrfachvererbung ermöglicht, wenn Sie einen Blick darauf werfen möchten.
Wenn Sie eine verkettete Vererbung wünschen (NICHT Mehrfachvererbung, aber für die meisten Menschen ist es dasselbe), kann dies mit einer Klasse wie der folgenden erreicht werden:
Dadurch bleibt die ursprüngliche Prototypenkette erhalten, aber es wird auch viel sinnloser Code ausgeführt.
quelle
Verwechseln Sie sich nicht mit JavaScript-Framework-Implementierungen mit Mehrfachvererbung.
Alles, was Sie tun müssen, ist, mit Object.create () jedes Mal ein neues Objekt mit dem angegebenen Prototypobjekt und den angegebenen Eigenschaften zu erstellen. Ändern Sie dann den Object.prototype.constructor in jedem Schritt des Weges, wenn Sie die Instanziierung
B
in planen möchten Zukunft.Erben Eigenschaften der Instanz
thisA
undthisB
verwenden wir Function.prototype.call () am Ende jeder Objektfunktion. Dies ist optional, wenn Sie nur den Prototyp erben möchten.Führen Sie irgendwo den folgenden Code aus und beachten Sie
objC
:B
erbt den Prototyp vonA
C
erbt den Prototyp vonB
objC
ist eine Instanz vonC
Dies ist eine gute Erklärung für die obigen Schritte:
OOP In JavaScript: Was Sie wissen müssen
quelle
Ich bin in keiner Weise ein Experte für Javascript OOP, aber wenn ich Sie richtig verstehe, möchten Sie so etwas wie (Pseudocode):
In diesem Fall würde ich etwas versuchen wie:
quelle
c.prototype
Mehrfaches Setzen führt nicht zu mehreren Prototypen. Wenn Sie zum Beispiel hättenAnimal.isAlive = true
,Cat.isAlive
wäre immer noch undefiniert.Es ist möglich, Mehrfachvererbung in JavaScript zu implementieren, obwohl dies nur sehr wenige Bibliotheken tun.
Ich könnte auf Ring.js verweisen , das einzige Beispiel, das ich kenne.
quelle
Ich habe heute viel daran gearbeitet und versucht, dies selbst in ES6 zu erreichen. Ich habe es mit Browserify, Babel und dann mit Wallaby getestet und es schien zu funktionieren. Mein Ziel ist es, das aktuelle Array zu erweitern, ES6, ES7 einzuschließen und einige zusätzliche benutzerdefinierte Funktionen hinzuzufügen, die ich im Prototyp für den Umgang mit Audiodaten benötige.
Wallaby besteht 4 meiner Tests. Die Datei example.js kann in die Konsole eingefügt werden, und Sie können sehen, dass sich die Eigenschaft 'include' im Prototyp der Klasse befindet. Ich möchte das morgen noch mehr testen.
Hier ist meine Methode: (Ich werde höchstwahrscheinlich nach etwas Schlaf als Modul umgestalten und neu verpacken!)
Github Repo: https://github.com/danieldram/array-includes-polyfill
quelle
Ich finde es lächerlich einfach. Das Problem hierbei ist, dass sich die untergeordnete Klasse nur auf
instanceof
die erste Klasse bezieht, die Sie aufrufenhttps://jsfiddle.net/1033xzyt/19/
quelle
Überprüfen Sie den Code, unter dem IS die Unterstützung für Mehrfachvererbung anzeigt. Geschehen mit PROTOTYPAL INHERITANCE
quelle
Ich habe durchaus die Funktion, Klassen mit Mehrfachvererbung definieren zu können. Es ermöglicht Code wie den folgenden. Insgesamt werden Sie eine vollständige Abweichung von den nativen Klassifizierungstechniken in Javascript feststellen (z. B. werden Sie das
class
Schlüsselwort nie sehen ):um eine Ausgabe wie diese zu erzeugen:
So sehen die Klassendefinitionen aus:
Wir können sehen, dass jede Klassendefinition, die die
makeClass
Funktion verwendet, einenObject
der übergeordneten Klassennamen akzeptiert , die übergeordneten Klassen zugeordnet sind. Es akzeptiert auch eine Funktion, dieObject
enthaltende Eigenschaften für die zu definierende Klasse zurückgibt . Diese Funktion hat einen Parameterprotos
, der genügend Informationen enthält, um auf eine Eigenschaft zuzugreifen, die von einer der übergeordneten Klassen definiert wurde.Das letzte Stück, das benötigt wird, ist die
makeClass
Funktion selbst, die ziemlich viel Arbeit leistet. Hier ist es, zusammen mit dem Rest des Codes. Ich habemakeClass
ziemlich stark kommentiert :Die
makeClass
Funktion unterstützt auch Klasseneigenschaften. Diese werden definiert, indem Eigenschaftsnamen mit dem$
Symbol versehen werden (beachten Sie, dass der endgültige Eigenschaftsname, der sich ergibt,$
entfernt wird). In diesem Sinne könnten wir eine spezielleDragon
Klasse schreiben , die den "Typ" des Drachen modelliert, wobei die Liste der verfügbaren Drachentypen in der Klasse selbst gespeichert wird, im Gegensatz zu den Instanzen:Die Herausforderungen der Mehrfachvererbung
Jeder, der den Code
makeClass
genau befolgt hat, wird ein ziemlich bedeutendes unerwünschtes Phänomen bemerken, das stillschweigend auftritt, wenn der obige Code ausgeführt wird: Das Instanziieren von aRunningFlying
führt zu ZWEI Aufrufen desNamed
Konstruktors!Dies liegt daran, dass das Vererbungsdiagramm folgendermaßen aussieht:
Wenn in einem Vererbungsdiagramm einer Unterklasse mehrere Pfade zu derselben übergeordneten Klasse vorhanden sind Klasse vorhanden sind, rufen Instanziierungen der Unterklasse den Konstruktor dieser übergeordneten Klasse mehrmals auf.
Dies zu bekämpfen ist nicht trivial. Schauen wir uns einige Beispiele mit vereinfachten Klassennamen an. Wir betrachten die Klasse
A
, die abstrakteste Elternklasse, die KlassenB
undC
, die beide erbenA
, und die Klasse,BC
die vonB
und erbtC
(und daher konzeptionell "doppelt erbt" vonA
):Wenn wir verhindern möchten, dass
BC
doppelt aufgerufen wird, müssenA.prototype.init
wir möglicherweise den Stil des direkten Aufrufs geerbter Konstruktoren aufgeben. Wir benötigen ein gewisses Maß an Indirektion, um zu überprüfen, ob doppelte Anrufe auftreten, und um sie kurzzuschließen, bevor sie auftreten.Wir konnten betrachten die Parameter an die Funktion Eigenschaften geliefert zu ändern: neben
protos
, eineObject
Rohdaten beschreiben vererbten Eigenschaften enthält, könnten wir auch eine Nutzenfunktion eine Instanz Methode so für den Aufruf , dass Eltern Methoden auch genannt werden, aber doppelte Anrufe werden erkannt und verhindert. Werfen wir einen Blick darauf, wo wir die Parameter für Folgendes festlegenpropertiesFn
Function
:Der gesamte Zweck der obigen Änderung
makeClass
besteht darin, dass wir ein zusätzliches Argument erhalten,propertiesFn
wenn wir uns aufrufenmakeClass
. Wir sollten auch bewusst sein , dass jede Funktion in jeder Klasse definiert nun einen Parameter , nachdem alle seine anderen empfangen kann, mit dem Namendup
, der eine ist ,Set
die alle Funktionen enthält , die bereits als Folge genannt wurden , von der geerbten Methode aufrufen:Mit diesem neuen Stil kann tatsächlich sichergestellt
"Construct A"
werden, dass nur einmal protokolliert wird, wenn eine Instanz vonBC
initialisiert wird. Es gibt jedoch drei Nachteile, von denen der dritte sehr kritisch ist :util.invokeNoDuplicates
Funktion verbirgt sich eine Menge Komplexität , und darüber nachzudenken, wie dieser Stil Mehrfachaufrufe vermeidet, ist nicht intuitiv und verursacht Kopfschmerzen. Wir haben auch diesen lästigendups
Parameter, der wirklich für jede einzelne Funktion in der Klasse definiert werden muss . Autsch.NiftyClass
eine Funktion überschreibtniftyFunction
undutil.invokeNoDuplicates(this, 'niftyFunction', ...)
sie ohne doppelten Aufruf ausführt,NiftyClass.prototype.niftyFunction
ruft sie die FunktionniftyFunction
jeder übergeordneten Klasse auf, die sie definiert, ignoriert alle Rückgabewerte dieser Klassen und führt schließlich die spezielle Logik von ausNiftyClass.prototype.niftyFunction
. Dies ist die einzig mögliche Struktur . WennNiftyClass
erbtCoolClass
undGoodClass
und beide übergeordneten KlassenniftyFunction
eigene Definitionen bereitstellen ,NiftyClass.prototype.niftyFunction
wird es niemals (ohne das Risiko eines Mehrfachaufrufs) möglich sein:NiftyClass
und dann die spezialisierte Logik von Elternklassen ausNiftyClass
an einem anderen Punkt aus, als nachdem alle spezialisierten übergeordneten Logik abgeschlossen wurdeniftyFunction
insgesamtNatürlich könnten wir jedes der oben genannten Probleme lösen, indem wir spezielle Funktionen definieren unter
util
:util.invokeNoDuplicatesSubClassLogicFirst(instance, fnName, ...)
util.invokeNoDuplicatesSubClassAfterParent(parentName, instance, fnName, ...)
(WoparentName
ist der Name des Elternteils, auf dessen spezialisierte Logik unmittelbar die spezialisierte Logik der Kinderklassen folgt?)util.invokeNoDuplicatesCanShortCircuitOnParent(parentName, testFn, instance, fnName, ...)
(In diesem FalltestFn
würde das Ergebnis der speziellen Logik für das genannte übergeordnete Element empfangenparentName
und eintrue/false
Wert zurückgegeben, der angibt, ob der Kurzschluss auftreten sollte.)util.invokeNoDuplicatesBlackListedParents(blackList, instance, fnName, ...)
(In diesem Fall handeltblackList
es sich um einenArray
übergeordneten Namen, dessen spezielle Logik insgesamt übersprungen werden sollte.)Diese Lösungen sind alle verfügbar, aber das ist totales Chaos ! Für jede eindeutige Struktur, die ein geerbter Funktionsaufruf annehmen kann, benötigen wir eine spezielle Methode, die unter definiert ist
util
. Was für eine absolute Katastrophe.Vor diesem Hintergrund können wir die Herausforderungen der Implementierung einer guten Mehrfachvererbung erkennen. Die vollständige Implementierung von
makeClass
I in dieser Antwort berücksichtigt nicht einmal das Problem des Mehrfachaufrufs oder viele andere Probleme, die im Zusammenhang mit der Mehrfachvererbung auftreten.Diese Antwort wird sehr lang. Ich hoffe, dass die
makeClass
Implementierung, die ich aufgenommen habe, immer noch nützlich ist, auch wenn sie nicht perfekt ist. Ich hoffe auch, dass jeder, der sich für dieses Thema interessiert, mehr Kontext gewonnen hat, um sich beim weiteren Lesen daran zu erinnern!quelle
Schauen Sie sich das Paket IeUnit an .
Die in IeUnit implementierte Konzeptassimilation scheint auf sehr dynamische Weise das zu bieten, wonach Sie suchen.
quelle
Hier ist ein Beispiel für die Verkettung von Prototypen mithilfe von Konstruktorfunktionen :
Dieses Konzept verwendet Yehuda Katz 'Definition einer "Klasse" für JavaScript:
Im Gegensatz zum Object.create-Ansatz müssen wir nicht wissen, von was jede "Klasse" erbt, wenn die Klassen auf diese Weise erstellt werden und wir Instanzen einer "Klasse" erstellen möchten. Wir benutzen nur
new
.Die Reihenfolge der Präzision sollte sinnvoll sein. Zuerst sieht es im Instanzobjekt aus, dann im Prototyp, dann im nächsten Prototyp usw.
Wir können auch die Prototypen ändern, die sich auf alle auf der Klasse erstellten Objekte auswirken.
Ich habe ursprünglich einiges davon mit dieser Antwort geschrieben .
quelle
child
erbt vonparent1
undparent2
). Ihr Beispiel spricht nur von einer Kette.Ein Neuling in der Szene ist SimpleDeclare . Wenn Sie sich jedoch mit Mehrfachvererbung befassen, erhalten Sie immer noch Kopien der ursprünglichen Konstruktoren. Das ist eine Notwendigkeit in Javascript ...
Merc.
quelle
Ich würde ds.oop verwenden . Es ähnelt prototype.js und anderen. macht Mehrfachvererbung sehr einfach und minimalistisch. (nur 2 oder 3 kb) Unterstützt auch einige andere nette Funktionen wie Schnittstellen und Abhängigkeitsinjektion
quelle
Wie wäre es damit, es implementiert Mehrfachvererbung in JavaScript:
Und hier ist der Code für die Dienstprogrammfunktion specialize_with ():
Dies ist echter Code, der ausgeführt wird. Sie können es kopieren, in eine HTML-Datei einfügen und selbst ausprobieren. Es funktioniert.
Das ist der Aufwand, MI in JavaScript zu implementieren. Nicht viel Code, eher Know-how.
Bitte schauen Sie sich meinen vollständigen Artikel dazu an, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS
quelle
Ich habe nur zugewiesen, welche Klassen ich in den Eigenschaften anderer benötige, und einen Proxy hinzugefügt, um ihnen automatisch zu zeigen, was mir gefällt:
quelle