Get to know MDN better
Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.
JavaScript ist eine prototypbasierte Sprache – das Verhalten eines Objekts wird durch seine eigenen Eigenschaften und die Eigenschaften seines Prototyps bestimmt. Mit der Einführung von Klassen ist die Erstellung von Objekthierarchien und die Vererbung von Eigenschaften und deren Werten jedoch viel stärker an andere objektorientierte Sprachen wie Java angelehnt. In diesem Abschnitt werden wir demonstrieren, wie Objekte aus Klassen erstellt werden können.
In vielen anderen Sprachen werden Klassen oder Konstruktoren klar von Objekten oder Instanzen unterschieden. In JavaScript sind Klassen hauptsächlich eine Abstraktion über den bestehenden mechanismus der prototypischen Vererbung – alle Muster können in prototypbasierte Vererbung umgewandelt werden. Klassen selbst sind auch normale JavaScript-Werte und haben ihre eigenen Prototypenketten. Tatsächlich können die meisten einfachen JavaScript-Funktionen als Konstruktoren verwendet werden – Sie verwenden den new-Operator mit einer Konstruktorfunktion, um ein neues Objekt zu erstellen.
Wir werden in diesem Tutorial mit dem gut abstrahierten Klassenmodell arbeiten und diskutieren, welche Semantik Klassen bieten. Wenn Sie tiefer in das zugrunde liegende Prototypsystem eintauchen möchten, können Sie den Leitfaden Vererbung und die Prototypen-Kette lesen.
In diesem Kapitel wird vorausgesetzt, dass Sie bereits ein gewisses Maß an Vertrautheit mit JavaScript haben und dass Sie gewöhnliche Objekte verwendet haben.
Wenn Sie bereits praktische Erfahrung mit JavaScript haben oder dem Leitfaden gefolgt sind, haben Sie wahrscheinlich schon Klassen verwendet, auch wenn Sie noch keine erstellt haben. Zum Beispiel dürfte dies Ihnen bekannt vorkommen:
In der ersten Zeile haben wir eine Instanz der Klasse Date erstellt und sie bigDay genannt. In der zweiten Zeile haben wir eine Methode toLocaleDateString() auf der bigDay-Instanz aufgerufen, die einen String zurückgibt. Dann haben wir zwei Zahlen verglichen: eine, die von der getTime()-Methode zurückgegeben wurde, die andere direkt von der Date-Klasse selbst aufgerufen, als Date.now().
Date ist eine eingebaute Klasse von JavaScript. Aus diesem Beispiel können wir einige grundlegende Ideen davon ableiten, was Klassen tun:
Diese entsprechen den drei Hauptmerkmalen von Klassen:
Klassen werden normalerweise mit Klassendeklarationen erstellt.
Innerhalb eines Klassenblocks stehen Ihnen eine Reihe von Funktionen zur Verfügung.
Wenn Sie aus einer Umgebung vor ES6 stammen, sind Sie möglicherweise besser damit vertraut, Funktionen als Konstruktoren zu verwenden. Das oben genannte Muster würde sich ungefähr mit Funktionskonstruktoren in Folgendes übersetzen:
Hinweis: Private Felder und Methoden sind neue Funktionen in Klassen, die keine triviale Entsprechung in Funktionskonstruktoren haben.
Nachdem eine Klasse deklariert wurde, können Sie Instanzen davon mit dem new-Operator erstellen.
Typische Funktionskonstruktoren können sowohl mit new konstruiert als auch ohne new aufgerufen werden. Der Versuch, eine Klasse ohne new zu "rufen", führt jedoch zu einem Fehler.
Im Gegensatz zu Funktionsdeklarationen werden Klassendeklarationen nicht gehoistet (oder in einigen Interpretationen gehoistet, aber mit der Einschränkung der temporalen Todeszone), was bedeutet, dass Sie eine Klasse nicht verwenden können, bevor sie deklariert ist.
Dieses Verhalten ist ähnlich wie bei Variablen, die mit let und const deklariert sind.
Ähnlich wie Funktionen haben auch Klassendeklarationen ihre Ausdrucksgegenstücke.
Klassenausdrücke können ebenfalls Namen haben. Der Name des Ausdrucks ist nur für den Klassenkörper sichtbar.
Vielleicht die wichtigste Aufgabe einer Klasse ist es, als "Fabrik" für Objekte zu fungieren. Zum Beispiel, wenn wir den Date-Konstruktor verwenden, erwarten wir, dass er ein neues Objekt gibt, das die Datumsinformationen darstellt, die wir übergeben haben — die wir dann mit anderen Methoden, die die Instanz bietet, manipulieren können. In Klassen erfolgt die Instanzerstellung durch den constructor.
Als Beispiel würden wir eine Klasse namens Color erstellen, die eine bestimmte Farbe repräsentiert. Benutzer erstellen Farben durch das Übergeben eines RGB-Triplets.
Öffnen Sie die Entwicklerwerkzeuge Ihres Browsers, fügen Sie den obigen Code in die Konsole ein und erstellen Sie dann eine Instanz:
Sie sollten eine Ausgabe wie diese sehen:
Object { values: (3) […] } values: Array(3) [ 255, 0, 0 ]Sie haben erfolgreich eine Color-Instanz erstellt, und die Instanz hat eine values-Eigenschaft, die ein Array der RGB-Werte ist, die Sie übergeben haben. Das ist ziemlich gleichbedeutend mit dem Folgenden:
Die Syntax eines Konstruktors ist genau die gleiche wie eine normale Funktion — was bedeutet, dass Sie andere Syntaxe wie Restparameter verwenden können:
Jedes Mal, wenn Sie new aufrufen, wird eine andere Instanz erstellt.
Innerhalb eines Klassenkonstruktors zeigt der Wert von this auf die neu erstellte Instanz. Sie können ihr Eigenschaften zuweisen oder bestehende Eigenschaften lesen (insbesondere Methoden — die wir als nächstes behandeln werden).
Der this-Wert wird automatisch als Ergebnis von new zurückgegeben. Es wird empfohlen, keinen Wert aus dem Konstruktor zurückzugeben — denn wenn Sie einen nicht-primitiven Wert zurückgeben, wird er zum Wert des new-Ausdrucks, und der Wert von this wird verworfen. (Sie können mehr darüber, was new tut, in seiner Beschreibung lesen.)
Wenn eine Klasse nur einen Konstruktor hat, unterscheidet sie sich nicht wesentlich von einer createX-Fabrikfunktion, die nur einfache Objekte erstellt. Der Vorteil von Klassen besteht jedoch darin, dass sie als "Vorlagen" verwendet werden können, die automatisch Methoden an Instanzen zuweisen.
Zum Beispiel können Sie für Date-Instanzen eine Reihe von Methoden verwenden, um unterschiedliche Informationen aus einem einzigen Datumswert zu erhalten, wie das Jahr, den Monat, den Wochentag usw. Sie können diese Werte auch über die setX-Gegenstücke wie setFullYear setzen.
Für unsere eigene Color-Klasse können wir eine Methode namens getRed hinzufügen, die den Rotwert der Farbe zurückgibt.
Ohne Methoden könnten Sie versucht sein, die Funktion innerhalb des Konstruktors zu definieren:
Das funktioniert auch. Ein Problem besteht jedoch darin, dass bei jeder Erstellung einer Color-Instanz eine neue Funktion erstellt wird, auch wenn sie alle das gleiche tun!
Im Gegensatz dazu wird eine Methode, wenn Sie sie verwenden, zwischen allen Instanzen geteilt. Eine Funktion kann zwischen allen Instanzen geteilt werden, aber ihr Verhalten kann sich ändern, wenn verschiedene Instanzen sie aufrufen, da der Wert von this unterschiedlich ist. Wenn Sie neugierig sind, wo diese Methode gespeichert wird — sie ist auf dem Prototyp aller Instanzen definiert, oder Color.prototype, was im Abschnitt Vererbung und die Prototypen-Kette ausführlicher erklärt wird.
Ähnlich können wir eine neue Methode namens setRed erstellen, die den Rotwert der Farbe setzt.
Sie fragen sich vielleicht: Warum sollten wir uns die Mühe machen, getRed- und setRed-Methoden zu verwenden, wenn wir direkt auf das values-Array auf der Instanz zugreifen können?
Es gibt eine Philosophie in der objektorientierten Programmierung namens "Kapselung". Das bedeutet, dass Sie nicht auf die zugrunde liegende Implementierung eines Objekts zugreifen sollten, sondern stattdessen gut abstrahierte Methoden verwenden sollten, um mit ihm zu interagieren. Zum Beispiel, wenn wir plötzlich entscheiden, Farben als HSL darzustellen:
Die Annahme des Benutzers, dass values den RGB-Wert bedeutet, bricht plötzlich zusammen und es kann dazu führen, dass ihre Logik fehlerhaft ist. Wenn Sie also ein Implementierer einer Klasse sind, würden Sie die interne Datenstruktur Ihrer Instanz von Ihrem Benutzer verbergen wollen, sowohl um die API sauber zu halten als auch um zu verhindern, dass der Code des Benutzers bricht, wenn Sie einige "harmlose Refaktorisierungen" vornehmen. In Klassen wird dies durch private Felder erreicht.
Ein privates Feld ist ein Bezeichner, der mit # (dem Rautezeichen) vorangestellt wird. Das Rautezeichen ist ein integraler Bestandteil des Namens des Feldes, was bedeutet, dass ein privates Feld niemals einen Namenskonflikt mit einem öffentlichen Feld oder einer Methode haben kann. Um auf ein privates Feld irgendwo in der Klasse zu verweisen, müssen Sie es im Klassenkörper deklarieren (Sie können kein privates Element im Handumdrehen erstellen). Abgesehen davon ist ein privates Feld so ziemlich ein normales Attribut.
Der Zugriff auf private Felder außerhalb der Klasse ist ein früher Syntaxfehler. Die Sprache kann dies verhindern, da #privateField eine spezielle Syntax ist, so dass sie eine statische Analyse durchführen und alle Verwendungen von privaten Feldern finden kann, bevor der Code überhaupt ausgewertet wird.
Hinweis: Code, der in der Chrome-Konsole ausgeführt wird, kann private Elemente außerhalb der Klasse zugreifen. Dies ist eine nur für DevTools vorkommende Lockerung der JavaScript-Syntax-Einschränkung.
Private Felder in JavaScript sind hart privat: Wenn die Klasse keine Methoden implementiert, die diese privaten Felder entblößen, gibt es absolut keinen Mechanismus, um sie von außerhalb der Klasse abzurufen. Das bedeutet, dass Sie sicher sind, jede Refaktorisierung an den privaten Feldern Ihrer Klasse vorzunehmen, solange das Verhalten der sichtbaren Methoden gleich bleibt.
Nachdem wir das values-Feld privat gemacht haben, können wir mehr Logik in den getRed und setRed-Methoden hinzufügen, anstatt sie zu einfachen Durchreichmethoden zu machen. Zum Beispiel können wir eine Überprüfung in setRed hinzufügen, um zu sehen, ob es sich um einen gültigen R-Wert handelt:
Wenn wir die values-Eigenschaft offen lassen, können unsere Benutzer diese Überprüfung leicht umgehen, indem sie direkt values[0] zuweisen und ungültige Farben erstellen. Aber mit einer gut gekapselten API können wir unseren Code robuster machen und Logikfehler in weiteren Code verhindern.
Eine Klassenmethode kann die privaten Felder anderer Instanzen lesen, solange sie derselben Klasse angehören.
Wenn anotherColor jedoch keine Color-Instanz ist, existiert #values nicht. (Auch wenn eine andere Klasse ein identisch benanntes privates Feld #values hat, bezieht es sich nicht auf das gleiche Ding und kann hier nicht zugegriffen werden.) Der Zugriff auf ein nicht vorhandenes privates Element wirft einen Fehler anstelle von undefined wie normale Eigenschaften. Wenn Sie nicht wissen, ob ein privates Feld auf einem Objekt existiert und Sie darauf zugreifen möchten, ohne try/catch zu verwenden, um den Fehler zu behandeln, können Sie den in-Operator verwenden.
Hinweis: Beachten Sie, dass das # eine spezielle Bezeichner-Syntax ist und Sie den Feldnamen nicht verwenden können, als ob er eine Zeichenfolge wäre. "#values" in anotherColor würde nach einer Eigenschaft namens "#values" suchen, anstelle eines privaten Feldes.
Es gibt einige Einschränkungen bei der Verwendung von privaten Elementen: derselbe Name kann nicht zweimal in einer einzigen Klasse deklariert werden und sie können nicht gelöscht werden. Beides führt zu frühen Syntaxfehlern.
Methoden, Getter und Setter können ebenfalls privat sein. Sie sind nützlich, wenn Sie etwas Komplexes haben, das intern von der Klasse erledigt werden muss, aber kein anderer Teil des Codes darf darauf zugreifen darf.
Stellen Sie sich zum Beispiel vor, Sie erstellen HTML-Custom-Elemente, die etwas recht Komplexes tun sollten, wenn sie geklickt/angeklickt/aktiviert werden. Außerdem sollten die recht komplexen Dinge, die geschehen, wenn das Element geklickt wird, auf diese Klasse beschränkt sein, da kein anderer Teil des JavaScript jemals darauf zugreifen wird (oder sollte).
In diesem Fall ist praktisch jedes Feld und jede Methode für die Klasse privat. Dies stellt somit eine Schnittstelle für den Rest des Codes dar, die im Wesentlichen genau wie ein eingebautes HTML-Element ist. Kein anderer Teil des Programms hat die Möglichkeit, die internen Struktur von Counter zu beeinflussen.
color.getRed() und color.setRed() ermöglichen es uns, den Rotwert einer Farbe zu lesen und zu schreiben. Wenn Sie aus Sprachen wie Java kommen, werden Sie mit diesem Muster sehr vertraut sein. Es ist jedoch in JavaScript immer noch etwas unergonomisch, Methoden zu verwenden, um einfach auf eine Eigenschaft zuzugreifen. Zugriffsfelder ermöglichen es uns, mit etwas zu arbeiten, als wäre es eine "tatsächliche Eigenschaft".
Es sieht so aus, als ob das Objekt eine Eigenschaft namens red hat - aber in Wirklichkeit gibt es keine solche Eigenschaft auf der Instanz! Es gibt nur zwei Methoden, aber sie sind mit get und set versehen, die es erlauben, sie zu manipulieren, als wären sie Eigenschaften.
Wenn ein Feld nur einen Getter hat, aber keinen Setter, wird es effektiv schreibgeschützt.
Im strict mode wirft die Zeile red.red = 0 einen Typfehler: "Kann Eigenschaft red von #<Color> nicht setzen, welche nur einen Getter hat". Im nicht-strengen Modus wird die Zuweisung stillschweigend ignoriert.
Private Felder haben auch ihre öffentlichen Gegenstücke, die es jeder Instanz ermöglichen, eine Eigenschaft zu haben. Felder sind normalerweise unabhängig von den Parametern des Konstruktors konzipiert.
Öffentliche Felder sind fast gleichbedeutend mit dem Zuweisen einer Eigenschaft zu this. Zum Beispiel kann das obige Beispiel auch in Folgendes umgewandelt werden:
Mit dem Date-Beispiel haben wir auch die Date.now()-Methode getroffen, die das aktuelle Datum zurückgibt. Diese Methode gehört zu keiner bestimmten Instanz — sie gehört zur Klasse selbst. Sie wird jedoch auf der Date-Klasse anstelle als einer globalen DateNow() Funktion belassen, weil sie hauptsächlich nützlich ist, wenn es sich um Datumsinstanzen handelt.
Hinweis: Das Präfixieren von Dienstprogrammmethoden mit dem, womit sie zu tun haben, wird als "Namensraumbildung" bezeichnet und gilt als eine gute Praxis. Zum Beispiel neben der alten, unpräfixierten parseInt() Methode, fügte JavaScript später zusätzlich die präfixierte Number.parseInt() Methode hinzu, um anzuzeigen, dass es um Zahlen geht.
Statische Eigenschaften sind eine Gruppe von Klassenfunktionen, die auf der Klasse selbst definiert sind, anstatt auf individuellen Instanzen der Klasse. Diese Funktionen umfassen:
Alles hat auch private Gegenstücke. Zum Beispiel können wir für unsere Color-Klasse eine statische Methode erstellen, die überprüft, ob ein bestimmtes Triplet ein gültiger RGB-Wert ist:
Statische Eigenschaften sind sehr ähnlich wie ihre Instanzgegenstücke, mit der Ausnahme, dass:
Es gibt auch ein spezielles Konstrukt, das als statischer Initialisierungsblock bezeichnet wird, bei dem es sich um einen Codeblock handelt, der ausgeführt wird, wenn die Klasse das erste Mal geladen wird.
Statische Initialisierungsblöcke sind fast gleichbedeutend damit, dass unmittelbar nach der Deklaration einer Klasse irgendein Code ausgeführt wird. Der einzige Unterschied besteht darin, dass sie Zugang zu statischen privaten Elementen haben.
Ein Hauptmerkmal, das Klassen mit sich bringen (neben der ergonomischen Kapselung mit privaten Feldern), ist die Vererbung, was bedeutet, dass ein Objekt einen großen Teil des Verhaltens eines anderen Objekts "ausleihen" kann, während es bestimmte Teile mit seiner eigenen Logik überschreibt oder verbessert.
Nehmen wir an, unsere Color-Klasse muss nun Transparenz unterstützen. Wir könnten versucht sein, ein neues Feld hinzuzufügen, das seine Transparenz angibt:
Dies bedeutet jedoch, dass jede Instanz - auch die überwiegende Mehrheit, die nicht transparent ist (mit einem Alphawert von 1) - den zusätzlichen Alphawert haben muss, was nicht sehr elegant ist. Außerdem, wenn die Funktionen weiter wachsen, wird unsere Color-Klasse sehr aufgeblasen und schwer zu warten.
Stattdessen würden wir in der objektorientierten Programmierung eine abgeleitete Klasse erstellen. Die abgeleitete Klasse hat Zugriff auf alle öffentlichen Eigenschaften der Elternklasse. In JavaScript werden abgeleitete Klassen mit einer extends-Klausel deklariert, die die Klasse angibt, von der sie abgeleitet wird.
Es gibt ein paar Dinge, die sofort Aufmerksamkeit erregt haben. Zuallererst ist, dass wir im Konstruktor super(r, g, b) aufrufen. Es ist eine Sprachvorgabe, super() aufzurufen, bevor Sie auf this zugreifen. Der Aufruf von super() ruft den Konstruktor der Elternklasse auf, um this zu initialisieren – hier ist es ungefähr gleichbedeutend mit this = new Color(r, g, b). Sie können vor super() Code haben, aber Sie dürfen nicht auf this zugreifen, bevor super() aufgerufen wurde – die Sprache verhindert, dass Sie auf das nicht initialisierte this zugreifen.
Nachdem die Elternklasse ihre Maßnahmen gegenüber this abgeschlossen hat, kann die abgeleitete Klasse ihre eigenen Logikmaßnahmen einführen. Hier haben wir ein privates Feld namens #alpha hinzugefügt und auch ein Paar Getter/Setter bereitgestellt, um mit ihnen zu interagieren.
Eine abgeleitete Klasse erbt alle Methoden ihrer Eltern. Betrachten Sie zum Beispiel den get red()-Accessor, den wir der Color hinzugefügt haben, im Abschnitt Zugriffsfelder - obwohl wir in ColorWithAlpha keine deklariert haben, können wir red dennoch aufrufen, da dieses Verhalten von der Elternklasse spezifiziert wird:
Abgeleitete Klassen können auch Methoden der Elternklasse überschreiben. Zum Beispiel erben alle Klassen implizit die Object-Klasse, die einige grundlegende Methoden wie toString() definiert. Die Standard-toString()-Methode ist jedoch notorisch nutzlos, da sie in den meisten Fällen [object Object] ausgibt:
Stattdessen kann unsere Klasse sie überschreiben, um die RGB-Werte der Farbe auszugeben:
Innerhalb abgeleiteter Klassen können Sie die Methoden der Elternklasse mit super aufrufen. Dies ermöglicht es Ihnen, Methoden zu erweitern und Code-Duplizierung zu vermeiden.
Wenn Sie extends verwenden, erben auch die statischen Methoden voneinander, sodass Sie diese ebenfalls überschreiben oder erweitern können.
Abgeleitete Klassen haben keinen Zugriff auf die privaten Felder der Elternklasse – dies ist ein weiterer wesentlicher Aspekt der "harten Privatsphäre" von JavaScript-privaten Feldern. Private Felder sind auf den Klassenkörper selbst beschränkt und gewähren keinem externen Code Zugriff.
Eine Klasse kann nur von einer Klasse erben. Dies verhindert Probleme bei mehrfacher Vererbung wie das Diamantproblem. Aufgrund der dynamischen Natur von JavaScript ist es jedoch immer noch möglich, den Effekt der mehrfachen Vererbung durch Klassenkomposition und Mixins zu erzielen.
Instanzen abgeleiteter Klassen sind auch Instanzen von der Basisklasse.
Der Leitfaden war bisher pragmatisch: Wir konzentrieren uns darauf, wie Klassen verwendet werden können, aber eine Frage bleibt unbeantwortet: Warum würde man eine Klasse verwenden? Die Antwort lautet: Es kommt darauf an.
Klassen führen ein Paradigma ein, oder eine Möglichkeit, Ihren Code zu organisieren. Klassen sind die Grundlagen der objektorientierten Programmierung, die auf Konzepten wie Vererbung und Polymorphismus (insbesondere Subtyp-Polymorphismus) aufgebaut ist. Viele Menschen sind jedoch philosophisch gegen bestimmte OOP-Praktiken eingestellt und verwenden Klassen daher nicht.
Ein Beispiel: Eines der Merkmale, das Date-Objekte berüchtigt macht, ist, dass sie änderbar sind.
Änderbarkeit und interner Zustand sind wichtige Aspekte der objektorientierten Programmierung, machen jedoch oft den Code schwer nachvollziehbar - da jeder scheinbar harmlose Vorgang unerwartete Nebeneffekte haben und das Verhalten in anderen Teilen des Programms ändern kann.
Um Code wiederzuverwenden, greifen wir häufig darauf zurück, Klassen zu erweitern, was große Hierarchien von Vererbungsmustern schaffen kann.
Es ist jedoch oft schwierig, Vererbung sauber zu beschreiben, wenn eine Klasse nur von einer anderen Klasse erben kann. Oft möchten wir das Verhalten mehrerer Klassen. In Java wird dies durch Schnittstellen erreicht; in JavaScript kann dies durch Mixins erreicht werden. Am Ende ist es jedoch immer noch nicht sehr bequem.
Auf der positiven Seite sind Klassen eine sehr mächtige Möglichkeit, unseren Code auf einer höheren Ebene zu organisieren. Ohne die Color-Klasse könnten wir zum Beispiel ein Dutzend Utility-Funktionen erstellen müssen:
Aber mit Klassen können wir sie alle unter dem Color-Namensraum versammeln, was die Lesbarkeit verbessert. Darüber hinaus ermöglicht die Einführung von privaten Feldern, bestimmte Daten vor nachgelagerten Benutzern zu verbergen, was eine saubere API schafft.
Im Allgemeinen sollten Sie überlegen, Klassen zu verwenden, wenn Sie Objekte erstellen möchten, die ihre eigenen internen Daten speichern und viel Verhalten freigeben. Nehmen Sie als Beispiel eingebaute JavaScript-Klassen:
JavaScript bietet die Möglichkeit, Ihren Code auf eine kanonische objektorientierte Weise zu organisieren, aber ob und wie man es verwendet, liegt ganz im Ermessen des Programmierers.
Der Bauplan für ein besseres Internet.
Besuche die gemeinnützige Muttergesellschaft der Mozilla Corporation, die Mozilla Foundation.
Teile dieses Inhalts sind ©1998–2026 von einzelnen mozilla.org-Mitwirkenden. Inhalte sind verfügbar unter einer Creative-Commons-Lizenz.