JavaScript new-Operator
Lerne, wie Konstruktorfunktionen und der new-Operator in JavaScript Objekte erzeugen: was new Schritt für Schritt tut, die Rückgabewert-Regel, new.target und Namenskonventionen.
Einführung in Konstruktoren und den new-Operator
In JavaScript sind Konstruktoren Funktionen, die dazu dienen, neu erstellte Objekte zu initialisieren. Sie spielen eine zentrale Rolle in der objektorientierten Programmierung, da Entwickler damit Eigenschaften und Verhaltensweisen definieren können, die Objekte einer bestimmten Klasse besitzen sollen. Der new-Operator wird verwendet, um eine Instanz eines Objekts auf Basis einer Konstruktorfunktion zu erzeugen – er richtet eine neue Objektumgebung basierend auf dem angegebenen Prototyp ein und führt den Konstruktor aus, um das neue Objekt zu initialisieren.
Wie Konstruktoren funktionieren
Eine Konstruktorfunktion in JavaScript sieht aus wie jede andere Funktion, wird aber per Konvention mit einem Großbuchstaben benannt, um sie von regulären Funktionen zu unterscheiden. Wenn der new-Operator eine Konstruktorfunktion aufruft, laufen hinter den Kulissen vier Dinge ab, die in etwa diesem Pseudocode entsprechen:
function User(name) {
// this = {}; (1) an empty object is created and assigned to this
// this.__proto__ = User.prototype; (2) prototype is linked
this.name = name; // (3) the constructor body runs, adding properties to this
// return this; (4) this is returned automatically
}- Ein neues leeres object wird erstellt und
thiszugewiesen. - Der Prototyp wird verknüpft: Das interne
[[Prototype]]des neuen Objekts wird auf dieprototype-Eigenschaft des Konstruktors gesetzt, sodass es dort definierte Eigenschaften und Methoden erbt. - Der Konstruktorrumpf wird ausgeführt: Die Funktion läuft mit den übergebenen Argumenten, und
thisverweist auf das brandneue object – Zuweisungen wiethis.name = namehängen so Eigenschaften daran. - Das object wird zurückgegeben:
thiswird automatisch zurückgegeben, es sei denn, der Konstruktor gibt explizit ein anderes object zurück (siehe Die Rückgabewert-Regel weiter unten).
Modernes JavaScript verwendet die class-Syntax, um Konstruktoren und Methoden intuitiver zu definieren. Dies bietet einen klareren, klassenbasierten Ansatz, der anderen Programmiersprachen ähnelt.
Beispiel: Einfache Konstruktorfunktion
Erklärung: In diesem Beispiel ist User eine Konstruktorfunktion, die name, age und eine greet-Methode auf neu erstellten Objekten initialisiert. Die Anweisung new User('John', 30) erzeugt eine neue Instanz von User mit dem Namen „John" und dem Alter 30. Innerhalb von greet verweist this auf das object, auf dem die Methode aufgerufen wurde.
Die Rückgabewert-Regel
Konstruktoren verwenden normalerweise kein return – das neue object (this) wird automatisch zurückgegeben. return hat jedoch ein spezielles, leicht zu übersehendes Verhalten innerhalb eines Konstruktors:
- Folgt auf
returnein object, wird dieses object anstelle vonthiszurückgegeben. - Folgt auf
returnein Primitiver Wert (string, number, boolean,undefinedusw.), wird er ignoriert undthiswie gewohnt zurückgegeben.
Erklärung: WithObject gibt ein einfaches object zurück, weshalb dieses die Instanz vollständig ersetzt. WithPrimitive gibt einen string zurück, der ignoriert wird – es wird das ursprüngliche this (mit name: 'Alice') zurückgegeben. Man verlässt sich selten darauf, aber es erklärt überraschende Ergebnisse, wenn ein Konstruktor versehentlich einen Wert zurückgibt.
new erkennen mit new.target
Innerhalb jeder Funktion ist new.target undefined, wenn die Funktion normal aufgerufen wird, und gleich der Funktion selbst, wenn sie mit new aufgerufen wird. Damit kann ein Konstruktor erkennen, wie er aufgerufen wurde – nützlich, um das new-Schlüsselwort zu erzwingen oder stillschweigend zu erlauben.
Erklärung: Da Modal new.target prüft, wird Modal('Without new') ohne new transparent an new Modal(...) weitergeleitet, sodass b trotzdem eine echte Modal-Instanz ist. (Hinweis: Viele Teams ziehen es vor, dies nicht zu tun und einen fehlenden new-Aufruf laut scheitern zu lassen.)
Wann sollte ich einen Konstruktor verwenden?
Verwende eine Konstruktorfunktion (oder eine class), wenn du viele Objekte gleicher Struktur erstellen musst – mehrere Benutzer, Autos, DOM-Widgets usw. Der Konstruktor zentralisiert die Einrichtungslogik, sodass jede Instanz auf die gleiche Weise gebaut wird und Methoden über den Prototyp teilt.
Benötigst du nur ein einzelnes object, ist ein einfaches object-Literal { ... } einfacher. Für ein einmaliges object, das dennoch von einem Konstruktorrumpf profitiert, kann man sogar einen anonymen Konstruktor verwenden:
Erklärung: Die anonyme function wird einmalig als Konstruktor ausgeführt und nirgendwo gespeichert, sodass sie nicht wiederverwendet werden kann – sie dient lediglich dazu, eine komplexe einmalige Einrichtung zu kapseln.
Konstruktorfunktionen vs. Klassen
Moderner Code bevorzugt in der Regel die class-Syntax, die im Wesentlichen syntaktischer Zucker über Konstruktorfunktionen und Prototypen ist. Die beiden folgenden Schnipsel sind gleichwertig:
Klassen bieten echte Vorteile gegenüber Konstruktorfunktionen: Methoden sind standardmäßig nicht aufzählbar, der Rumpf läuft im Strict Mode, das Aufrufen einer Klasse ohne new wirft einen Fehler, und extends/super machen Vererbung deutlich sauberer. Konstruktorfunktionen sind dennoch wichtig zu verstehen, da Klassen auf demselben Prototyp-Mechanismus aufbauen und man ihnen in älterem Code noch begegnet.
Konstruktoren für komplexe Objekte verwenden
Konstruktoren können eingesetzt werden, um komplexere Beziehungen zwischen Objekten aufzubauen, einschließlich Methoden, die mit anderen Eigenschaften der Objekte interagieren.
Beispiel: Konstruktor mit Methoden
Erklärung: Der Car-Konstruktor stattet jedes Auto-object mit spezifischen Eigenschaften und einer Methode aus, die Informationen über das Auto anzeigt.
Beispiel: Prototyp-Methoden
Erklärung: Indem introduce dem Employee-Prototyp hinzugefügt wird, teilen alle Instanzen dieselbe Methode, was speichereffizienter ist als die Definition direkt im Konstruktor.
Es wird empfohlen, ES6-Klassen zur Definition von Objekten und Konstruktoren zu verwenden, um saubereren und besser lesbaren Code zu schreiben.
Best Practices mit Konstruktoren
Beim Arbeiten mit Konstruktoren in JavaScript können bestimmte Best Practices die Lesbarkeit, Effizienz und Skalierbarkeit des Codes erheblich verbessern. Nachfolgend werden die Praktiken mit ausführlichen Beispielen und Erklärungen erläutert:
1. Namenskonvention
Best Practice: Beginne Konstruktornamen immer mit einem Großbuchstaben, um sie von regulären Funktionen zu unterscheiden. Dies ist eine gängige Konvention in JavaScript und vielen anderen Programmiersprachen, die Entwicklern hilft, Konstruktorfunktionen schnell zu erkennen.
Beispiel:
Erklärung: Die Konstruktorfunktion Laptop beginnt mit einem Großbuchstaben, was darauf hinweist, dass sie mit dem new-Operator zur Erstellung neuer Objekte verwendet werden soll.
2. Logik trennen
Best Practice: Für Methoden, die keinen Zugriff auf individuelle Instanzdaten benötigen, definiere sie lieber auf dem Prototyp des Konstruktors als innerhalb des Konstruktors selbst. Dieser Ansatz spart Speicher, da alle Instanzen dieselbe Methode teilen, anstatt dass jede Instanz eine neue Funktion im Speicher erzeugt.
Beispiel:
Erklärung: Die describe-Methode wird dem Book-Prototyp hinzugefügt, was bedeutet, dass alle Instanzen von Book dieselbe describe-Methode teilen. Dies ist effizienter, als describe innerhalb des Konstruktors zu definieren, was für jede Buchinstanz eine neue Funktion ergeben würde.
3. Rückgabewerte
Best Practice: Vermeide es, Werte aus Konstruktoren zurückzugeben. JavaScript-Konstruktoren geben automatisch die neue Objektinstanz zurück, sofern nicht explizit ein anderes object zurückgegeben wird. Das Zurückgeben von Nicht-object-Werten (wie ein string oder eine Zahl) hat keine Wirkung – die neue Instanz wird trotzdem zurückgegeben.
Beispiel:
Erklärung: Obwohl versucht wird, einen string aus dem Player-Konstruktor zurückzugeben, ignoriert JavaScript diesen Rückgabewert, weil er kein object ist. Die neue Player-Instanz wird wie erwartet zurückgegeben.
Fazit
Das Verstehen und der Einsatz von Konstruktoren und dem new-Operator in JavaScript ist für effektive objektorientierte Programmierung unerlässlich. Indem man die hier beschriebenen Konventionen und Best Practices befolgt, können Entwickler organisierten, effizienten und skalierbaren Code erstellen. Konstruktoren bieten einen leistungsstarken Mechanismus zur Initialisierung neuer Objekte und zur strukturierten und intuitiven Definition ihres Verhaltens.
Um tiefer einzutauchen, erforsche, wie Konstruktoren Verhalten über prototypische Vererbung teilen, wie die class-Syntax auf diesen Ideen aufbaut und wie eine Funktion auch ein object mit eigenen Eigenschaften ist.