Vor ES6 gab es viele Verwirrung über die Unterschiede zwischen einer Factory-Funktion und einer Konstruktorfunktion in JavaScript. Da ES6 das Schlüsselwort `class` hat, scheinen viele Leute zu denken, dass es viele Probleme mit Konstruktorfunktionen gelöst hat. Lassen Sie uns die Hauptunterschiede untersuchen, die Sie noch kennen müssen.,
Schauen wir uns zunächst Beispiele an:
Jede dieser Strategien speichert Methoden in einem gemeinsam genutzten Prototyp und unterstützt optional private Daten über Konstruktorfunktionsschließungen. Mit anderen Worten, sie haben meist die gleichen Eigenschaften und könnten meist austauschbar verwendet werden.
In JavaScript kann jede Funktion ein neues Objekt zurückgeben. Wenn es sich nicht um eine Konstruktorfunktion oder-klasse handelt, wird sie als Factory-Funktion bezeichnet.,
ES6 klassen desugar zu konstruktor funktionen, so alles, was folgt über konstruktor funktionen gilt auch für ES6 klassen:
class Foo {}console.log(typeof Foo); // function
Konstruktoren kraft anrufer zu verwenden die new
schlüsselwort. Das ist es, aber das hat einige relevante Nebenwirkungen.
Was macht das Schlüsselwort new
?,
Hinweis: Wir verwenden
instance
, um auf die neu erstellte Instanz zu verweisen, undConstructor
, um auf die Konstruktorfunktion oder-klasse zu verweisen, die die Instanz erstellt hat.
- Instanziiert ein neues Instanzobjekt und bindet
this
innerhalb des Konstruktors daran. - Bindet
instance.__proto__
anConstructor.prototype
. - Als Nebeneffekt von 2 bindet
instance.__proto__.constructor
anConstructor
., - gibt implizit
this
zurück, was sich aufinstance
bezieht.
Vorteile von Konstruktoren & `class`
- In den meisten Büchern lernen Sie, Klassen oder Konstruktoren zu verwenden.
-
this
bezieht sich auf das neue Objekt. - Manche Leute mögen die Art und Weise
myFoo = new Foo()
liest. - Es kann einen Leistungsvorteil bei der Mikrooptimierung geben, aber Sie sollten sich darüber keine Sorgen machen, es sei denn, Sie haben Ihren Code profiliert und bewiesen, dass dies ein Problem für Sie ist.,
Nachteile von Konstruktoren & `class`
Vor ES6 war das Vergessen von new
ein sehr häufiger Fehler. Um dem entgegenzuwirken, haben viele Leute boilerplate verwendet, um es zu erzwingen:
function Foo() {
if (!(this instanceof Foo)) { return new Foo(); }
}
In ES6+ (ES2015) Wenn Sie versuchen, einen Klassenkonstruktor ohne new
aufzurufen, wird immer ein Fehler ausgegeben. Es ist nicht möglich zu vermeiden, die new
– Anforderung für Aufrufer zu erzwingen, ohne Ihre Klasse in eine Factory-Funktion einzuschließen.,
Details der Instanziierung werden in die aufrufende API durchgesickert (über die Anforderung „neu“).
Alle Aufrufer sind eng an die Konstruktorimplementierung gekoppelt. Wenn Sie jemals die zusätzliche Flexibilität des Werks benötigen, ist der Refactor eine grundlegende Änderung. Klasse Fabrik refactors sind Häufig genug, dass Sie erscheinen in der Samenflüssigkeit Refactoring Buch „Refactoring: Improving the Design of existing Code“ von Martin Fowler, Kent Beck, John Brant, William Opdyke und Don Roberts.,
Konstruktoren brechen das Open / Closed-Prinzip
Aufgrund der new
– Anforderung verletzen Konstruktorfunktionen das Open / Closed-Prinzip: Eine API sollte zur Erweiterung geöffnet, aber zur Änderung geschlossen sein.
Ich argumentiere, dass die Klasse zu Factory Refactor üblich genug ist, dass sie als Standarderweiterung für alle Konstruktoren betrachtet werden sollte: Ein Upgrade von einer Klasse zu einer Factory sollte keine Dinge kaputt machen, aber in JavaScript tut es das.,
Wenn Sie mit dem Exportieren eines Konstruktors oder einer Klasse beginnen und Benutzer den Konstruktor verwenden, stellen Sie fest, dass Sie die Flexibilität einer Factory benötigen (z. B. um die Implementierung auf Objektpools umzustellen oder über Ausführungskontexte hinweg zu instanziieren oder um mehr Vererbungsflexibilität mit alternativen Prototypen zu erzielen), können Sie dies nicht einfach tun, ohne einen Refaktor für Aufrufer zu erzwingen.,
Leider ist in JavaScript der Wechsel von einem Konstruktor oder einer Klasse zu einer Fabrik eine grundlegende Änderung:
Im obigen Beispiel beginnen wir mit einer Klasse, aber wir möchten die Möglichkeit hinzufügen, verschiedene Arten von Auto-Bundles anzubieten. Dazu verwendet das Werk alternative Prototypen für verschiedene Fahrzeugbündel. Ich habe diese Technik verwendet, um verschiedene Implementierungen einer Media Player-Schnittstelle zu speichern und den richtigen Prototyp basierend auf dem Medientyp auszuwählen, den der Player steuern musste.,
Die Verwendung von Konstruktoren ermöglicht das trügerische ‚instanceof‘
Eine der wichtigsten Änderungen im Konstruktor an factory refactor ist instanceof
. Manchmal sind Leute versucht, instanceof
als Typprüfschutz in ihrem Code zu verwenden. Das kann sehr problematisch sein. Ich empfehle Ihnen, instanceof
zu vermeiden.
`instanceof` liegt.,
instanceof
führt keine Typprüfung so durch, wie Sie ähnliche Überprüfungen in stark typisierten Sprachen erwarten. Stattdessen wird eine Identitätsprüfung durchgeführt, bei der das __proto__
– Objekt des Objekts mit der Constructor.prototype
– Eigenschaft verglichen wird.
Es funktioniert nicht über verschiedene Speicherbereiche wie Iframes hinweg, zum Beispiel (eine häufige Fehlerquelle in JavaScript-Einbettungen von Drittanbietern). Es funktioniert auch nicht, wenn Ihre Constructor.prototype
ersetzt wird.,
Es schlägt auch fehl, wenn Sie mit einer Klasse oder einem Konstruktor beginnen (der this
zurückgibt, der mit der Constructor.prototype
verknüpft ist) und dann zum Exportieren eines beliebigen Objekts wechseln (nicht verknüpft mit der Constructor.prototype
), was passiert, wenn Sie von einem Konstruktor zu einer Fabrik wechseln.
Kurz gesagt, instanceof
ist eine weitere Möglichkeit, von einem Konstruktor zu einer Factory zu wechseln.
Vorteile der Verwendung der Klasse
- Bequeme, in sich geschlossene Syntax.,
- Eine einzige, kanonische Möglichkeit, Klassen in JavaScript zu emulieren. Vor ES6, gab es mehrere konkurrierende Implementierungen in gängigen Bibliotheken.
- Mehr vertraut mit Menschen aus einem klassenbasierten Sprachhintergrund.
Nachteile der Verwendung der Klasse
Alle Konstruktornachteile sowie:
- Versuchung für Benutzer, problematische Klassenhierarchien mit dem Schlüsselwort
extends
zu erstellen.,
Klassenhierarchien führen zu einer Reihe bekannter Probleme im objektorientierten Design, einschließlich des fragilen Basisklassenproblems, des Gorilla-Bananenproblems, des Duplizierungsproblems durch Notwendigkeit und so weiter. Leider bietet Klasse erstreckt sich wie Bälle leisten werfen und Stühle leisten Sitzen. Lesen Sie dazu „Die beiden Säulen von JavaScript: Prototypal OO“ und „Inside the Dev Team Death Spiral“.,
Es ist erwähnenswert, dass sowohl Konstruktoren als auch Fabriken auch zum Erstellen problematischer Vererbungshierarchien verwendet werden können, aber mit dem Schlüsselwort `extends` erstellt class eine Erschwinglichkeit, die Sie auf den falschen Weg führt. Mit anderen Worten, es ermutigt Sie, in Bezug auf unflexible (und oft falsche) Is-a-Beziehungen zu denken, anstatt die flexibleren kompositorischen Has-a-oder Can-Do-Beziehungen.
Eine Affordance ist eine Funktion, die die Möglichkeit bietet, eine bestimmte Aktion auszuführen., Zum Beispiel ermöglicht ein Knopf das Verdrehen, ein Hebel das Ziehen, ein Knopf das Drücken usw.
Die Vorteile der Verwendung von Fabriken
Fabriken sind viel flexibler als Konstruktorfunktionen oder Klassen und führen die Menschen nicht auf den falschen Weg, indem sie sie mit dem Schlüsselwort „extends“ und tiefen Vererbungshierarchien verführen. Es gibt viele sicherere Mechanismen zur Wiederverwendung von Code, die Sie gegenüber der Klassenvererbung bevorzugen sollten, einschließlich Funktionen und Modulen.,
Geben Sie ein beliebiges Objekt zurück und verwenden Sie einen beliebigen Prototyp
Sie können beispielsweise problemlos verschiedene Arten von Objekten erstellen, die dieselbe API implementieren, z. B. einen Media Player, der Player für mehrere instanziieren kann Arten von Videoinhalten, die verschiedene APIs unter der Haube verwenden, oder eine Ereignisbibliothek, die DOM-Ereignisse oder Web-Socket-Ereignisse ausgeben kann.
Fabriken können Objekte auch über Ausführungskontexte hinweg instanziieren, Objektpools nutzen und flexiblere prototypische Vererbungsmodelle ermöglichen.,
Keine Refactoring Sorgen
Sie würden nie eine Notwendigkeit haben, von einer Fabrik zu einem Konstruktor zu konvertieren, so Refactoring wird nie ein Problem sein.
Keine `neuen`
Keine Zweideutigkeit über die Verwendung von new
. (Dadurch verhält sich this
schlecht, siehe nächsten Punkt).
Standard` dieses ‚Verhalten
this
verhält sich wie gewohnt, sodass Sie damit auf das übergeordnete Objekt zugreifen können. Zum Beispiel bezieht sichplayer.create()
,this
auf player, genau wie jeder andere Methodenaufruf., call()
und apply()
weisen Sie auch this
erwartungsgemäß neu zu.
Manche Leute mögen die Art und Weise, wie` MyFoo = createFoo () ‚ liest
- Erstellt keinen Link von der Instanz zu
Factory.prototype
— aber das ist eigentlich eine gute Sache, weil Sie keine trügerischeinstanceof
. Stattdessen schlägtinstanceof
immer fehl. Siehe Vorteile. -
this
bezieht sich nicht auf das neue Objekt in der Fabrik. Siehe Vorteile., - Es kann langsamer als eine Konstruktorfunktion in Mikrooptimierungs-Benchmarks ausführen. Der langsame Weg ist immer noch sehr schnell Millionen von ops/sec auf einem alten computer. Dies ist eher ein Problem im Bibliotheks – oder Framework-Code als im Anwendungscode. Benchmark immer aus der Benutzerperspektive, bevor Mikrooptimierungen verwendet werden.
Schlussfolgerung
Meiner Meinung nach hat class
möglicherweise eine bequeme Syntax, aber das kann die Tatsache nicht ausgleichen, dass es unvorsichtige Benutzer dazu bringt, auf den Felsen der Klassenvererbung zum Absturz zu bringen., Es ist auch riskant, da Sie in Zukunft möglicherweise ein Upgrade auf eine Factory durchführen möchten, aber alle Ihre Aufrufer aufgrund des Schlüsselworts new
eng mit der Konstruktorfunktion verbunden sind und die Tatsache, dass der Wechsel von Klassen zu Fabriken eine wichtige Änderung darstellt.
Sie denken vielleicht, dass Sie die Aufrufseiten einfach umgestalten können, aber in großen Teams oder wenn die Klasse, mit der Sie arbeiten, Teil einer öffentlichen API ist, können Sie Code brechen, der nicht in Ihrer Kontrolle ist. Mit anderen Worten, Sie können nicht immer davon ausgehen, dass Refactoring-Anrufer sogar eine Option sind.,
Das Coole an Fabriken ist, dass sie nicht nur leistungsfähiger und flexibler sind, sondern auch die einfachste Möglichkeit, ganze Teams und ganze API-Benutzerbasen zu ermutigen, Muster zu verwenden, die einfach, flexibel und sicher sind.