Definition von Klassen oder Objekten in ECMAScript
- Vorherige Seite Objektscope
- Nächste Seite Objektänderungen
Die Verwendung vorgegebener Objekte ist nur ein Teil der Fähigkeit objektorientierter Sprachen, ihre wahre Stärke liegt in der Möglichkeit, eigene spezialisierte Klassen und Objekte zu erstellen.
ECMAScript verfügt über viele Methoden zur Erstellung von Objekten oder Klassen.
Fertigungsweise
Der ursprüngliche Weg
Weil die Eigenschaften eines Objekts dynamisch definiert werden können, nachdem es erstellt wurde, schreiben viele Entwickler bei der ersten Einführung von JavaScript ähnliche Codes wie folgt:
var oCar = new Object; oCar.color = "blue"; oCar.doors = 4; oCar.mpg = 25; oCar.showColor = function() { alert(this.color); };
Im obigen Code wird das Objekt car erstellt und einige Eigenschaften zugewiesen: seine Farbe ist blau, es hat vier Türen und kann 25 Meilen pro Gallone fahren. Der letzte Attribut ist ein Zeiger auf eine Funktion, was bedeutet, dass es sich um eine Methode handelt. Nach Ausführung dieses Codes kann das Objekt car verwendet werden.
Allerdings gibt es hier ein Problem, nämlich dass möglicherweise mehrere car-Instanzen erstellt werden müssen.
Lösung: Fabrikmethode
Um dieses Problem zu lösen, haben Entwickler Fabrikfunktionen geschaffen, die das Erstellen und Zurückgeben spezifischer Objekttypen ermöglichen.
Zum Beispiel kann die Funktion createCar() verwendet werden, um die vorab aufgelisteten Operationen zur Erstellung des car-Objekts zu verpacken:
function createCar() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; } var oCar1 = createCar(); var oCar2 = createCar();
Hier sind alle Codes der ersten Beispiel in der Funktion createCar() enthalten. Außerdem gibt es eine zusätzliche Zeile, die das car-Objekt (oTempCar) als Funktionswert zurückgibt. Durch Aufruf dieser Funktion wird ein neues Objekt erstellt und alle notwendigen Eigenschaften erhalten, was ein car-Objekt ist, das wir zuvor beschrieben haben. Auf diese Weise können wir leicht zwei Versionen von car-Objekten (oCar1 und oCar2) erstellen, deren Eigenschaften vollständig identisch sind.
Parameter an die Funktion übergeben
Wir können die Funktion createCar() auch ändern, indem wir ihr Standardwerte für die verschiedenen Eigenschaften übergeben, anstatt einfach Standardwerte für die Eigenschaften zu vergeben:
function createCar(sColor,iDoors,iMpg) { var oTempCar = new Object; oTempCar.color = sColor; oTempCar.doors = iDoors; oTempCar.mpg = iMpg; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; } var oCar1 = createCar("red",4,23); var oCar2 = createCar("blue",3,25); oCar1.showColor(); // Ausgabe "red" oCar2.showColor(); // Ausgabe "blue"
Durch das Hinzufügen von Parametern an die Funktion createCar() können die Eigenschaften color, doors und mpg des zu erstellenden car-Objekts zugewiesen werden. Dies ermöglicht es, zwei Objekte mit gleichen Eigenschaften, aber verschiedenen Eigenschaftswerten zu haben.
Methoden außerhalb der Fabrikfunktion definieren
Obwohl ECMAScript immer formeller wird, werden die Methoden zur Objekterstellung vernachlässigt und ihre Normalisierung wird bis heute abgelehnt. Ein Teil ist auf semantische Gründe zurückzuführen (es sieht nicht so professionell aus, wie die Verwendung des Konstruktors new Operator), ein Teil auf funktionale Gründe. Die funktionalen Gründe liegen darin, dass mit diesem Ansatz Methoden erstellt werden müssen. In den vorangegangenen Beispielen muss bei jeder Aufrufung der Funktion createCar() eine neue Funktion showColor() erstellt werden, was bedeutet, dass jeder Objekt seine eigene Version von showColor() hat. Und tatsächlich teilen alle Objekte die gleiche Funktion.
Einige Entwickler definieren die Methoden des Objekts außerhalb der Fabrikfunktion und verweisen darauf über Eigenschaften, um dieses Problem zu vermeiden:
function showColor() { alert(this.color); } function createCar(sColor,iDoors,iMpg) { var oTempCar = new Object; oTempCar.color = sColor; oTempCar.doors = iDoors; oTempCar.mpg = iMpg; oTempCar.showColor = showColor; return oTempCar; } var oCar1 = createCar("red",4,23); var oCar2 = createCar("blue",3,25); oCar1.showColor(); // Ausgabe "red" oCar2.showColor(); // Ausgabe "blue"
In diesem Code, der neu geschrieben wurde, wird die Funktion showColor() vor der Funktion createCar() definiert. Innerhalb von createCar() wird einem Objekt ein Zeiger auf die bereits existierende Funktion showColor() zugewiesen. Funktionell löst dies das Problem der mehrfachen Erstellung von Funktionsobjekten; aber semantisch ähnelt die Funktion nicht der Methode eines Objekts.
Diese Probleme haben allevon Entwicklern definiertAuftreten des Konstruktors.
Konstruktionsmethode
Die Erstellung eines Konstruktors ist so einfach wie die Erstellung einer Fabrikfunktion. Der erste Schritt ist die Auswahl des Klassennamens, also des Namens des Konstruktors. Laut Konvention wird der erste Buchstabe dieses Namens großgeschrieben, um ihn von Variablen mit kleinem Anfangsbuchstaben zu unterscheiden. Außer diesem Unterschied sehen Konstruktoren sehr ähnlich wie Fabrikfunktionen aus. Betrachten Sie das folgende Beispiel:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.showColor = function() { alert(this.color); }; } var oCar1 = new Car("red",4,23); var oCar2 = new Car("blue",3,25);
Hier wird Ihnen die Unterschiede zwischen dem obigen Code und der Fabrikmethode erklärt. Zunächst wird im Konstruktor kein Objekt erstellt, sondern der this-Schlüssel verwendet. Wenn der Konstruktor mit dem new-Operator aufgerufen wird, wird vor der Ausführung des ersten Zeilencodes ein Objekt erstellt, und nur durch this kann auf dieses Objekt zugegriffen werden. Dann kann this direkt Attributen zugewiesen werden, und in der Regel ist das die Rückgabewert des Konstruktors (es ist nicht erforderlich, den return-Operator ausdrücklich zu verwenden).
Mit dem new-Operator und dem Klassennamen Car erstellt man Objekte, die sich mehr wie die Erstellung allgemeiner ECMAScript-Objekte verhalten.
Vielleicht fragen Sie sich, ob dieses Verfahren das gleiche Problem bei der Verwaltung von Funktionen wie das vorherige Verfahren hat? Ja.
Wie eine Fabrikfunktion erstellt der Konstruktor wiederholt Funktionen und erstellt für jedes Objekt eine独立的 Funktion. Allerdings, wie mit der Fabrikfunktion ähnlich, kann der Konstruktor auch mit einer externen Funktion überschrieben werden, was semantisch keinen Sinn macht. Dies ist genau der Vorteil des Prototyp-Modus, den wir weiter besprechen werden.
Prototyp-Modus
Diese Methode nutzt das prototype-Attribut des Objekts und kann als das Muster betrachtet werden, das für die Erstellung neuer Objekte abhängig ist.
Hier wird zunächst mit einem leeren Konstruktor der Klassennamen gesetzt. Dann werden alle Eigenschaften und Methoden direkt der prototype-Eigenschaft zugewiesen. Wir haben den vorangegangenen Beispielcode so geändert:
function Car() { } Car.prototype.color = "blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car(); var oCar2 = new Car();
In diesem Code definiert man zunächst den Konstruktor (Car), der keine Codezeilen enthält. Die folgenden Zeilen fügen durch Hinzufügen von Eigenschaften zur prototype-Eigenschaft des Car-Objekts die Eigenschaften des Car-Objekts. Wenn new Car() aufgerufen wird, werden alle Eigenschaften des Prototyps sofort dem zu erstellenden Objekt zugewiesen, was bedeutet, dass alle Car-Instanzen Zeiger auf die showColor()-Funktion speichern. Semantisch gesehen gehören alle Eigenschaften einem Objekt, was die Probleme der beiden vorangegangenen Methoden löst.
Darüber hinaus ermöglicht diese Methode die Verwendung des instanceof-Betreibers, um den Typ des auf das Objekt verwiesenen Variablen zu überprüfen. Daher gibt das folgende Codebeispiel TRUE aus:
alert(oCar1 instanceof Car); // Ausgabe "true"
Problem des Prototyp-Modus
Der Prototyp-Modus scheint eine gute Lösung zu sein. Leider ist das nicht der Fall.
Zunächst hat dieser Konstruktor keine Parameter. Da die Prototyp-Methode es nicht ermöglicht, Parameter an den Konstruktor zu übergeben, um den Wert der Eigenschaften zu initialisieren, da die Eigenschaften color, doors und mpg von Car1 und Car2 alle "blue", 4 und 25 betragen, bedeutet dies, dass die Standardwerte der Eigenschaften erst nach der Erstellung des Objekts geändert werden können, was sehr lästig ist, aber noch nicht alles. Das eigentliche Problem tritt auf, wenn die Eigenschaften auf Objekte und nicht auf Funktionen verweisen. Die Funktionssharing verursacht kein Problem, aber Objekte werden selten von mehreren Instanzen gemeinsam genutzt. Überlegen Sie sich den folgenden Beispiel:
function Car() { } Car.prototype.color = "blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.drivers = new Array("Mike","John"); Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car(); var oCar2 = new Car(); oCar1.drivers.push("Bill"); alert(oCar1.drivers); // Ausgabe "Mike,John,Bill" alert(oCar2.drivers); // Ausgabe "Mike,John,Bill"
Im obigen Code ist die Eigenschaft drivers ein Zeiger auf ein Array-Objekt, das die Namen "Mike" und "John" enthält. Da drivers ein Referenzwert ist, verweisen die beiden Instanzen von Car beide auf dasselbe Array. Dies bedeutet, dass der Wert "Bill" zu oCar1.drivers hinzugefügt wird, und dieser Wert kann auch in oCar2.drivers gesehen werden. Die Ausgabe eines jeden dieser Zeiger zeigt den String "Mike,John,Bill" an.
Da es bei der Erstellung von Objekten so viele Probleme gibt, werden Sie sich sicherlich fragen, ob es eine vernünftige Methode zur Erstellung von Objekten gibt. Die Antwort ist ja, und es erfordert die Kombination von Konstruktor und Prototypenmethode.
Mischung aus Konstruktor/Prototypenmethode
Durch die Kombination von Konstruktor und Prototypenmethode können Objekte wie in anderen Programmiersprachen erstellt werden. Dieses Konzept ist sehr einfach: Die nicht-funktionalen Eigenschaften des Objekts werden mit dem Konstruktor definiert, die funktionalen Eigenschaften (Methoden) des Objekts mit dem Prototypenmuster definiert. Das Ergebnis ist, dass alle Funktionen nur einmal erstellt werden und jedes Objekt seine eigenen Instanzen der Objekteigenschaften hat.
Wir haben den vorherigen Beispielcode geändert, wie folgt:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); } Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car("red",4,23); var oCar2 = new Car("blue",3,25); oCar1.drivers.push("Bill"); alert(oCar1.drivers); // Ausgabe "Mike,John,Bill" alert(oCar2.drivers); // Ausgabe "Mike,John"
Heute ähnelt es mehr der Erstellung eines allgemeinen Objekts. Alle nicht-funktionalen Eigenschaften werden im Konstruktor erstellt, was bedeutet, dass wieder Defaultwerte für Eigenschaften mit den Parametern des Konstruktors zugewiesen werden können. Da nur eine Instanz der Funktion showColor() erstellt wird, gibt es keine Speicherverschwendung. Außerdem beeinflusst die Hinzufügung des Wertes "Bill" zur drivers-Array von oCar1 die Array von oCar2 nicht, daher zeigt oCar1.drivers "Mike,John,Bill" an, während oCar2.drivers "Mike,John" anzeigt. Da das Prototypenmuster verwendet wird, kann der Operator instanceof immer noch verwendet werden, um den Typ des Objekts zu bestimmen.
Diese Methode ist die Hauptmethode, die ECMAScript verwendet, und sie hat die Eigenschaften anderer Methoden, ohne ihre Nebenwirkungen zu haben. Dennoch denken einige Entwickler, dass diese Methode nicht perfekt genug ist.
Dynamische Prototyp-Methode
Für Entwickler, die an anderen Sprachen gewöhnt sind, fühlt sich die gemischte Konstruktions-/Prototyp-Methode nicht so harmonisch an. Schließlich wird bei der Definition von Klassen in den meisten objektorientierten Sprachen sowohl Attribute als auch Methoden visuell verpackt. Überlegen Sie sich die folgende Java-Klasse:
class Car { public String color = "blue"; public int doors = 4; public int mpg = 25; public Car(String color, int doors, int mpg) { this.color = color; this.doors = doors; this.mpg = mpg; } public void showColor() { System.out.println(color); } }
Java verpackt die Attribute und Methoden der Car-Klasse hervorragend, daher kann man anhand dieses Codes erkennen, was sie implementieren soll und welche Funktionalität sie definiert. Kritiker der gemischten Konstruktions-/Prototyp-Methode argue, dass es logisch unzusammenhängend ist, Attribute im Konstruktor zu finden und Methoden außerhalb desselben zu suchen. Daher haben sie die dynamische Prototyp-Methode entwickelt, um eine freundlichere Codestil zu bieten.
Die Grundidee der dynamischen Prototyp-Methode ist ähnlich wie die gemischte Konstruktions-/Prototyp-Methode, d.h. nicht-funktionale Attribute werden im Konstruktor definiert, während funktionale Attribute über die Prototyp-Attribute definiert werden. Der einzige Unterschied ist die Position, an der Methoden dem Objekt zugewiesen werden. Hier ist die Car-Klasse, die mit der dynamischen Prototyp-Methode neu geschrieben wurde:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); if (typeof Car._initialized == "undefined") { Car.prototype.showColor = function() { alert(this.color); }; Car._initialized = true; } }
Bis typeof Car._initialized "undefined" ist, bleibt dieser Konstrukturfunktor unverändert. Dies ist der wichtigste Teil der dynamischen Prototypenmethode. Wenn dieser Wert nicht definiert ist, wird der Konstrukturfunktor in der Prototypenmethode fortgesetzt, um die Methoden des Objekts zu definieren, und Car._initialized wird auf true gesetzt. Wenn dieser Wert definiert ist (sein Wert ist true, der Wert von typeof ist Boolean), wird diese Methode nicht mehr erstellt. Kurz gesagt, diese Methode verwendet ein Zeichen (_initialized), um zu bestimmen, ob dem Prototyp bereits Methoden hinzugefügt wurden. Diese Methode wird nur einmal erstellt und zugewiesen, und traditionelle OOP-Entwickler werden sich freuen zu entdecken, dass dieser Code dem Klassendefiniton in anderen Sprachen ähnlicher ist.
Misch-Fabrikmethode
Diese Methode wird oft als Ausweichlösung verwendet, wenn die vorherige Methode nicht angewendet werden kann. Ihr Ziel ist es, gefälschte Konstrukturfunktionen zu erstellen, die nur neue Instanzen anderer Objekte zurückgeben.
Dieser Code sieht sehr ähnlich aus wie eine Fabrikfunktion:
function Car() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar; }
Im Gegensatz zur klassischen Methode verwendet diese Methode den Operator new, so dass sie wie ein echter Konstrukturfunktor aussieht:
var car = new Car();
Da der Operator new im Car()-Konstrukturfunktor aufgerufen wird, wird der zweite Operator new (außerhalb des Konstrukturfunktors) ignoriert und das im Konstrukturfunktor intern erstellte Objekt wird an die Variable car zurückgegeben.
Diese Methode hat die gleichen Probleme im internen Management von Objektmethoden wie die klassische Methode. Es wird dringend empfohlen: Vermeiden Sie diese Methode, es sei denn, es gibt keinen anderen Ausweg.
Welche Methode verwenden
Wie bereits erwähnt, wird zurzeit am häufigsten die gemischte Konstruktions-/Prototypenmethode verwendet. Außerdem sind dynamische primitive Methoden sehr beliebt und äquivalent in der Funktion zur Konstruktions-/Prototypenmethode. Man kann jede dieser beiden Methoden verwenden. Verwenden Sie jedoch nicht die klassische Konstruktions- oder Prototypenmethode alleine, da dies Probleme in den Code einführen könnte.
Beispiel
Ein interessantes Merkmal der Objekte ist ihre Art, Probleme zu lösen. Ein häufiges Problem in ECMAScript ist die Leistung der Stringverbindung. Ähnlich wie in anderen Sprachen sind die Strings in ECMAScript unveränderlich, das heißt, ihr Wert kann nicht geändert werden. Überlegen Sie sich das folgende Codebeispiel:
var str = "hello "; str += "world";
Tatsächlich erfolgen die folgenden Schritte im Hintergrund dieses Codes:
- Erstellen Sie eine Zeichenfolge zum Speichern von "hello ";
- Erstellen Sie eine Zeichenfolge zum Speichern von "world".
- Erstellen Sie eine Zeichenfolge zum Speichern der verknüpften Ergebnisse.
- Kopieren Sie den aktuellen Inhalt von str in das Ergebnis.
- Kopieren Sie "world" in das Ergebnis.
- Aktualisieren Sie str, sodass er auf das Ergebnis verweist.
Jedes Mal, wenn eine Zeichenfolgenvielfachung abgeschlossen wird, werden die Schritte 2 bis 6 ausgeführt, was diese Operation sehr ressourcenintensiv macht. Wenn dieser Prozess mehrere hundert oder sogar tausend Male wiederholt wird, kann dies zu Leistungproblemen führen. Die Lösung besteht darin, Zeichenfolgen in ein Array-Objekt zu speichern und dann die join()-Methode (Parameter ist leerer String) zum Erstellen der endgültigen Zeichenfolge zu verwenden. Stellen Sie sich vor, der folgende Code ersetzt den vorherigen Code:
var arr = new Array(); arr[0] = "hello "; arr[1] = "world"; var str = arr.join("");
Dadurch wird es kein Problem sein, egal wie viele Zeichenfolgen in die Gruppe eingefügt werden, da die Verbindung nur bei Aufruf der join()-Methode erfolgt. In diesem Fall erfolgen die folgenden Schritte:
- Erstellen Sie eine Zeichenfolge zum Speichern des Ergebnisses
- Kopieren Sie jede Zeichenfolge an den geeigneten Ort im Ergebnis
Obwohl diese Lösung gut ist, gibt es noch bessere Methoden. Das Problem ist, dass dieses Code nicht genau seine Absicht widerspiegelt. Um es besser verständlich zu machen, kann die Funktion diese Funktion mit dem StringBuffer-Klasse verpackt werden:
function StringBuffer () { this._strings_ = new Array(); } StringBuffer.prototype.append = function(str) { this._strings_.push(str); }; StringBuffer.prototype.toString = function() { return this._strings_.join(""); };
Diese Codezeile beachtet zunächst die Eigenschaft strings, die ursprünglich als private Eigenschaft gedacht war. Sie verfügt über zwei Methoden, nämlich append() und toString() Methode. Die append() Methode hat einen Parameter, der diesen Parameter an die Zeichenfolgengruppe anhängt, und die toString() Methode ruft die join-Methode der Gruppe auf, um die tatsächlich verbundenen Zeichenfolgen zurückzugeben. Um eine Gruppe von Zeichenfolgen mit dem StringBuffer-Objekt zu verbinden, kann der folgende Code verwendet werden:
var buffer = new StringBuffer(); buffer.append("hello "); buffer.append("world"); var result = buffer.toString();
Testen Sie die Leistung des StringBuffer-Objekts und der traditionellen Stringkombinationsmethode mit dem folgenden Code:
var d1 = new Date(); var str = ""; for (var i=0; i < 10000; i++) { str += "text"; } var d2 = new Date(); document.write("Kombination mit Plus: ") + (d2.getTime() - d1.getTime()) + " Millisekunden"); var buffer = new StringBuffer(); d1 = new Date(); for (var i=0; i < 10000; i++) { buffer.append("text"); } var result = buffer.toString(); d2 = new Date(); document.write("<br />Kombination mit StringBuffer: ") + (d2.getTime() - d1.getTime()) + " Millisekunden");
Dieser Code führt zwei Tests zur Stringkombination durch, den ersten mit dem Pluszeichen und den zweiten mit der StringBuffer-Klasse. Jede Operation verbindet 10000 Strings. Die Datumswerte d1 und d2 dienen zur Bestimmung der Zeit, die für die Operation erforderlich ist. Beachten Sie, dass ein Date-Objekt ohne Parameter das aktuelle Datum und die aktuelle Uhrzeit erhält. Um die Zeit zu berechnen, die für die Kombination erforderlich ist, subtractieren Sie die Millisekundenangaben der Daten (zurückgegeben durch die Methode getTime()) voneinander. Dies ist eine gängige Methode zur Messung der JavaScript-Leistung. Die Ergebnisse dieses Tests können Ihnen helfen, die Effizienzunterschiede zwischen der Verwendung der StringBuffer-Klasse und dem Pluszeichen zu vergleichen.
- Vorherige Seite Objektscope
- Nächste Seite Objektänderungen