In der objektorientierten Programmierung ist eine Klasse eine erweiterbare Programmcode-Vorlage zum Erstellen von Objekten, die Anfangswerte für Zustände (Mitgliedsvariablen) und Verhaltensimplementierungen (Mitgliedsfunktionen oder -methoden) bereitstellt.
In der Praxis müssen wir oft viele Objekte derselben Art erstellen, etwa Benutzer, Waren oder was auch immer.
Wie wir bereits aus dem Kapitel Konstruktor, Operator „new“, wissen, kann new function
dabei helfen.
Aber im modernen JavaScript gibt es ein fortgeschritteneres „Klassen“-Konstrukt, das großartige neue Funktionen einführt, die für die objektorientierte Programmierung nützlich sind.
Die grundlegende Syntax lautet:
Klasse MyClass { // Klassenmethoden Konstruktor() { ... } method1() { ... } method2() { ... } method3() { ... } ... }
Verwenden Sie dann new MyClass()
um ein neues Objekt mit allen aufgelisteten Methoden zu erstellen.
Die Methode constructor()
wird von new
automatisch aufgerufen, sodass wir das Objekt dort initialisieren können.
Zum Beispiel:
Klasse Benutzer { Konstruktor(Name) { this.name = Name; } sayHi() { alarm(this.name); } } // Verwendung: let user = new User("John"); user.sayHi();
Wenn new User("John")
aufgerufen wird:
Ein neues Objekt wird erstellt.
Der constructor
wird mit dem angegebenen Argument ausgeführt und weist es this.name
zu.
…Dann können wir Objektmethoden aufrufen, wie zum Beispiel user.sayHi()
.
Kein Komma zwischen Klassenmethoden
Eine häufige Gefahr für unerfahrene Entwickler besteht darin, ein Komma zwischen Klassenmethoden zu setzen, was zu einem Syntaxfehler führen würde.
Die Notation hier ist nicht mit Objektliteralen zu verwechseln. Innerhalb der Klasse sind keine Kommas erforderlich.
Was genau ist also eine class
? Das ist keine völlig neue Entität auf Sprachebene, wie man meinen könnte.
Lassen Sie uns jegliche Magie enthüllen und sehen, was eine Klasse wirklich ist. Das wird zum Verständnis vieler komplexer Aspekte beitragen.
In JavaScript ist eine Klasse eine Art Funktion.
Schauen Sie hier:
Klasse Benutzer { Konstruktor(Name) { this.name = name; } sayHi() { Alert(this.name); } } // Beweis: Benutzer ist eine Funktion Alert(Typ des Benutzers); // Funktion
Was class User {...}
-Konstrukt wirklich macht, ist:
Erstellt eine Funktion namens User
, die zum Ergebnis der Klassendeklaration wird. Der Funktionscode stammt aus der constructor
(wird als leer angenommen, wenn wir eine solche Methode nicht schreiben).
Speichert Klassenmethoden wie sayHi
in User.prototype
.
Nachdem new User
Benutzerobjekt erstellt wurde und wir seine Methode aufrufen, wird es aus dem Prototyp übernommen, genau wie im Kapitel F.prototype beschrieben. Das Objekt hat also Zugriff auf Klassenmethoden.
Wir können das Ergebnis der class User
wie folgt darstellen:
Hier ist der Code zur Selbstprüfung:
Klasse Benutzer { Konstruktor(Name) { this.name = name; } sayHi() { Alert(this.name); } } // Klasse ist eine Funktion Alert(Typ des Benutzers); // Funktion // ...oder genauer gesagt die Konstruktormethode Alert(User === User.prototype.constructor); // WAHR // Die Methoden sind in User.prototype, z. B.: alarm(User.prototype.sayHi); // der Code der sayHi-Methode // Im Prototyp gibt es genau zwei Methoden Alert(Object.getOwnPropertyNames(User.prototype)); // Konstruktor, sayHi
Manchmal sagen Leute, dass die class
ein „syntaktischer Zucker“ ist (eine Syntax, die die Lesbarkeit erleichtern soll, aber nichts Neues einführt), weil wir tatsächlich dasselbe deklarieren könnten, ohne das Schlüsselwort class
überhaupt zu verwenden:
// Klasse User in reine Funktionen umschreiben // 1. Konstruktorfunktion erstellen Funktion Benutzer(name) { this.name = Name; } // Ein Funktionsprototyp hat standardmäßig die Eigenschaft „Konstruktor“, // also müssen wir es nicht erstellen // 2. Methode zum Prototyp hinzufügen User.prototype.sayHi = function() { alarm(this.name); }; // Verwendung: let user = new User("John"); user.sayHi();
Das Ergebnis dieser Definition ist ungefähr das gleiche. Es gibt also tatsächlich Gründe, warum class
als syntaktischer Zucker zur Definition eines Konstruktors zusammen mit seinen Prototypmethoden betrachtet werden kann.
Dennoch gibt es wichtige Unterschiede.
Zunächst wird eine von class
erstellte Funktion durch eine spezielle interne Eigenschaft [[IsClassConstructor]]: true
gekennzeichnet. Es ist also nicht ganz dasselbe wie die manuelle Erstellung.
Die Sprache sucht an verschiedenen Stellen nach dieser Eigenschaft. Im Gegensatz zu einer regulären Funktion muss sie beispielsweise mit new
aufgerufen werden:
Klasse Benutzer { Konstruktor() {} } Alert(Typ des Benutzers); // Funktion Benutzer(); // Fehler: Klassenkonstruktor-Benutzer kann nicht ohne „new“ aufgerufen werden
Außerdem beginnt eine Zeichenfolgendarstellung eines Klassenkonstruktors in den meisten JavaScript-Engines mit „class…“.
Klasse Benutzer { Konstruktor() {} } Warnung(Benutzer); // Klasse Benutzer { ... }
Es gibt noch weitere Unterschiede, wir werden sie bald sehen.
Klassenmethoden sind nicht aufzählbar. Eine Klassendefinition setzt das enumerable
für alle Methoden im "prototype"
auf false
“.
Das ist gut, denn wenn wir ein Objekt for..in
, wollen wir normalerweise nicht seine Klassenmethoden.
Klassen use strict
. Der gesamte Code innerhalb des Klassenkonstrukts befindet sich automatisch im strikten Modus.
Darüber hinaus bietet class
viele weitere Funktionen, die wir später untersuchen werden.
Genau wie Funktionen können Klassen innerhalb eines anderen Ausdrucks definiert, weitergegeben, zurückgegeben, zugewiesen usw. werden.
Hier ist ein Beispiel für einen Klassenausdruck:
let User = class { sayHi() { alarm("Hallo"); } };
Ähnlich wie benannte Funktionsausdrücke können Klassenausdrücke einen Namen haben.
Wenn ein Klassenausdruck einen Namen hat, ist er nur innerhalb der Klasse sichtbar:
// „Benannter Klassenausdruck“ // (kein solcher Begriff in der Spezifikation, aber das ähnelt dem benannten Funktionsausdruck) let User = class MyClass { sayHi() { alarm(MyClass); // Der Name meiner Klasse ist nur innerhalb der Klasse sichtbar } }; neuer Benutzer().sayHi(); // funktioniert, zeigt MyClass-Definition alarm(MyClass); // Fehler, MyClass-Name ist außerhalb der Klasse nicht sichtbar
Wir können Klassen sogar dynamisch „on-demand“ erstellen, wie folgt:
Funktion makeClass(phrase) { // eine Klasse deklarieren und zurückgeben Rückgabeklasse { sayHi() { Warnung(Satz); } }; } // Eine neue Klasse erstellen let User = makeClass("Hallo"); neuer Benutzer().sayHi(); // Hallo
Genau wie Literalobjekte können Klassen Getter/Setter, berechnete Eigenschaften usw. enthalten.
Hier ist ein Beispiel für user.name
das mit get/set
implementiert wurde:
Klasse Benutzer { Konstruktor(Name) { // ruft den Setter auf this.name = Name; } get name() { return this._name; } set name(value) { if (value.length < 4) { alarm("Name ist zu kurz."); zurückkehren; } this._name = value; } } let user = new User("John"); Alert(Benutzername); // John user = neuer Benutzer(""); // Name ist zu kurz.
Technisch gesehen funktioniert eine solche Klassendeklaration durch die Erstellung von Gettern und Settern in User.prototype
.
Hier ist ein Beispiel mit einem berechneten Methodennamen mit Klammern [...]
:
Klasse Benutzer { ['say' + 'Hallo']() { alarm("Hallo"); } } neuer Benutzer().sayHi();
Solche Merkmale sind leicht zu merken, da sie denen von wörtlichen Objekten ähneln.
Alte Browser benötigen möglicherweise eine Polyfüllung
Klassenfelder sind eine neue Ergänzung der Sprache.
Bisher hatten unsere Klassen nur Methoden.
„Klassenfelder“ ist eine Syntax, die das Hinzufügen beliebiger Eigenschaften ermöglicht.
Fügen wir beispielsweise der class User
die Eigenschaft name
hinzu:
Klasse Benutzer { name = „John“; sayHi() { alarm(`Hallo, ${this.name}!`); } } neuer Benutzer().sayHi(); // Hallo, John!
Also schreiben wir einfach „
Der wichtige Unterschied zwischen Klassenfeldern besteht darin, dass sie für einzelne Objekte und nicht User.prototype
festgelegt werden:
Klasse Benutzer { name = „John“; } let user = new User(); Alert(Benutzername); // John alarm(User.prototype.name); // undefiniert
Wir können Werte auch mithilfe komplexerer Ausdrücke und Funktionsaufrufe zuweisen:
Klasse Benutzer { name = prompt("Name, bitte?", "John"); } let user = new User(); Alert(Benutzername); // John
Wie im Kapitel Funktionsbindungsfunktionen in JavaScript gezeigt, haben sie ein dynamisches this
. Das hängt vom Kontext des Anrufs ab.
Wenn also eine Objektmethode weitergegeben und in einem anderen Kontext aufgerufen wird, ist this
kein Verweis mehr auf ihr Objekt.
Dieser Code zeigt beispielsweise undefined
an:
Klassenschaltfläche { Konstruktor(Wert) { this.value = value; } click() { alarm(this.value); } } let button = new Button("hello"); setTimeout(button.click, 1000); // undefiniert
Das Problem heißt „ this
verlieren“.
Es gibt zwei Ansätze zur Behebung, wie im Kapitel Funktionsbindung erläutert:
Übergeben Sie eine Wrapper-Funktion, z. B. setTimeout(() => button.click(), 1000)
.
Binden Sie die Methode an ein Objekt, z. B. im Konstruktor.
Klassenfelder bieten eine weitere, recht elegante Syntax:
Klassenschaltfläche { Konstruktor(Wert) { this.value = value; } click = () => { alarm(this.value); } } let button = new Button("hello"); setTimeout(button.click, 1000); // Hallo
Das Klassenfeld click = () => {...}
wird pro Objekt erstellt. Für jedes Button
-Objekt gibt es eine separate Funktion, die this
auf das Objekt verweist. Wir können button.click
überall weitergeben, und der Wert this
wird immer korrekt sein.
Dies ist besonders in Browserumgebungen für Ereignis-Listener nützlich.
Die grundlegende Klassensyntax sieht folgendermaßen aus:
Klasse MyClass { prop = Wert; // Eigentum Konstruktor(...) { // Konstruktor // ... } method(...) {} // Methode get Something(...) {} // Getter-Methode set Something(...) {} // Setter-Methode [Symbol.iterator]() {} // Methode mit berechnetem Namen (Symbol hier) // ... }
MyClass
ist technisch gesehen eine Funktion (diejenige, die wir als constructor
bereitstellen), während Methoden, Getter und Setter in MyClass.prototype
geschrieben werden.
In den nächsten Kapiteln erfahren wir mehr über Klassen, einschließlich Vererbung und anderen Funktionen.
Wichtigkeit: 5
Die Clock
Klasse (siehe Sandbox) ist im funktionalen Stil geschrieben. Schreiben Sie es in der „Klasse“-Syntax um.
PS: Die Uhr tickt in der Konsole, öffnen Sie sie, um zu sehen.
Öffnen Sie eine Sandbox für die Aufgabe.
Klasse Uhr { Konstruktor({ template }) { this.template = Vorlage; } render() { let date = new Date(); let hours = date.getHours(); if (Stunden < 10) Stunden = '0' + Stunden; let mins = date.getMinutes(); if (Minuten < 10) Minuten = '0' + Minuten; let secs = date.getSeconds(); if (Sek. < 10) Sek. = '0' + Sek.; let Output = this.template .replace('h', Stunden) .replace('m', Min.) .replace('s', secs); console.log(Ausgabe); } stoppen() { clearInterval(this.timer); } Start() { this.render(); this.timer = setInterval(() => this.render(), 1000); } } let clock = new Clock({template: 'h:m:s'}); clock.start();
Öffnen Sie die Lösung in einer Sandbox.