Einführung
Dieses Kapitel ist der zweite Teil über die objektorientierte Implementierung von ECMAScript. Im ersten Teil haben wir die Einführung und den Vergleich von CEMAScript besprochen, bevor Sie mit diesem Kapitel fortfahren dass Sie den ersten Teil 1 Artikel gelesen haben, da dieser Artikel zu lang ist (35 Seiten).
Englischer Originaltext:http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
Hinweis: Aufgrund der Länge dieses Artikels sind Fehler unvermeidlich und werden ständig überarbeitet.
In der Einleitung haben wir ECMAScript erweitert. Wenn wir nun seine OOP-Implementierung kennen, definieren wir es genau:
Datentyp
Obwohl ECMAScript eine dynamisch schwach typisierte Sprache ist, die Typen dynamisch konvertieren kann, verfügt sie dennoch über Datentypen. Mit anderen Worten: Ein Objekt muss zu einem echten Typ gehören.
In der Standardspezifikation sind 9 Datentypen definiert, in ECMAScript-Programmen kann jedoch nur auf 6 direkt zugegriffen werden: Undefiniert, Null, Boolean, String, Number und Object.
Auf die anderen drei Typen kann nur auf Implementierungsebene zugegriffen werden (ECMAScript-Objekte können diese Typen nicht verwenden) und werden in Spezifikationen verwendet, um einige Betriebsverhaltensweisen zu erklären und Zwischenwerte zu speichern. Diese 3 Typen sind: Referenz, Liste und Vervollständigung.
Daher wird die Referenz zur Erläuterung von Operatoren wie delete, typeof und this verwendet und enthält ein Basisobjekt und einen Eigenschaftsnamen. Die Liste beschreibt das Verhalten der Parameterliste (in neuen Ausdrücken und Funktionsaufrufen wird Vervollständigung verwendet). um das Verhalten von Break-, Continue-, Return- und Throw-Anweisungen zu erklären.
Primitive Werttypen
Wenn man auf die 6 Datentypen zurückblickt, die in ECMAScript-Programmen verwendet werden, sind die ersten 5 primitive Werttypen, darunter Undefiniert, Null, Boolean, String, Zahl und Objekt.
Beispiel für einen primitiven Werttyp:
Diese Werte werden direkt auf der untersten Ebene implementiert. Sie sind keine Objekte, daher gibt es keinen Prototyp oder Konstruktor.
Anmerkung des Onkels: Obwohl diese nativen Werte im Namen denen ähneln, die wir normalerweise verwenden (Boolean, String, Number, Object), sind sie nicht dasselbe. Daher sind die Ergebnisse von typeof(true) und typeof(Boolean) unterschiedlich, da das Ergebnis von typeof(Boolean) eine Funktion ist, sodass die Funktionen Boolean, String und Number Prototypen haben (auch im Kapitel über Lese- und Schreibattribute weiter unten erwähnt). .
Wenn Sie wissen möchten, um welche Art von Daten es sich handelt, verwenden Sie am besten typeof. Es gibt ein Beispiel, auf das Sie achten müssen. Wenn Sie typeof verwenden, um den Typ von Null zu bestimmen, ist das Ergebnis Objekt. Warum? Weil der Typ Null als Null definiert ist.
Die Spezifikation kann dies nicht erklären, aber Brendan Eich (der Erfinder von JavaScript) bemerkte, dass null hauptsächlich an Stellen verwendet wird, an denen Objekte erscheinen, im Gegensatz zu undefiniert, wie zum Beispiel beim Festlegen eines Objekts auf eine Nullreferenz. Einige Leute führten es jedoch in einigen Dokumenten auf einen Fehler zurück und fügten den Fehler in die Fehlerliste ein, sodass auch Brendan Eich an der Diskussion teilnahm. Das Ergebnis war, dass das Ergebnis von typeof null auf object gesetzt wurde (trotz der 262-3). Der Standard definiert, dass der Typ von Null Null ist, und 262-5 hat den Standard geändert, um zu sagen, dass der Typ von Null Objekt ist.
Objekttyp
Als nächstes ist der Objekttyp (nicht zu verwechseln mit dem Objektkonstruktor, wir diskutieren jetzt nur abstrakte Typen) der einzige Datentyp, der ECMAScript-Objekte beschreibt.
Objekt ist eine ungeordnete Sammlung von Schlüssel-Wert-Paaren.
Ein Objekt ist eine ungeordnete Sammlung von Schlüssel-Wert-Paaren
Der Schlüsselwert eines Objekts wird als Attribut bezeichnet, und ein Attribut ist ein Container für Grundwerte und andere Objekte. Wenn der Wert eines Attributs eine Funktion ist, nennen wir ihn eine Methode.
Zum Beispiel:
Dynamisch
Wie wir in Kapitel 17 dargelegt haben, sind Objekte in ES vollständig dynamisch. Das bedeutet, dass wir die Eigenschaften des Objekts nach Belieben hinzufügen, ändern oder löschen können, während das Programm ausgeführt wird.
Zum Beispiel:
Einige Eigenschaften können nicht geändert werden (schreibgeschützte Eigenschaften, gelöschte Eigenschaften oder nicht konfigurierbare Eigenschaften). Wir werden es später in den Attributeigenschaften erklären.
Darüber hinaus legt die ES5-Spezifikation fest, dass statische Objekte nicht mit neuen Eigenschaften erweitert werden können und ihre Eigenschaftenseiten nicht gelöscht oder geändert werden können. Es handelt sich um sogenannte eingefrorene Objekte, die durch Anwendung der Methode Object.freeze(o) erhalten werden können.
In der ES5-Spezifikation wird die Methode Object.preventExtensions(o) auch verwendet, um Erweiterungen zu verhindern, oder die Methode Object.defineProperty(o) wird verwendet, um Eigenschaften zu definieren:
Eingebaute Objekte, native Objekte und Hostobjekte
Es ist zu beachten, dass die Spezifikation auch zwischen integrierten Objekten, Elementobjekten und Hostobjekten unterscheidet.
Eingebaute Objekte und Elementobjekte werden durch die ECMAScript-Spezifikation definiert und implementiert, und die Unterschiede zwischen den beiden sind unbedeutend. Alle von ECMAScript implementierten Objekte sind native Objekte (einige davon sind integrierte Objekte, andere werden bei der Ausführung des Programms erstellt, z. B. benutzerdefinierte Objekte). Integrierte Objekte sind eine Teilmenge nativer Objekte, die vor dem Programmstart in ECMAScript integriert werden (z. B. parseInt, Match usw.). Alle Hostobjekte werden von der Hostumgebung bereitgestellt, normalerweise vom Browser, und können Fenster, Warnungen usw. umfassen.
Beachten Sie, dass das Hostobjekt möglicherweise von ES selbst implementiert wird und dabei der Semantik der Spezifikation vollständig entspricht. Unter diesem Gesichtspunkt können sie (theoretisch so bald wie möglich) als „native Host“-Objekte bezeichnet werden, die Spezifikation definiert jedoch nicht das Konzept von „nativen Host“-Objekten.
Boolesche, String- und Zahlenobjekte
Darüber hinaus definiert die Spezifikation auch einige native spezielle Verpackungsklassen. Diese Objekte sind:
1. Boolesches Objekt
2. String-Objekt
3. Digitale Objekte
Diese Objekte werden über die entsprechenden integrierten Konstruktoren erstellt und enthalten native Werte als interne Eigenschaften. Diese Objekte können primitive Werte konvertieren und umgekehrt.
Darüber hinaus gibt es Objekte, die von speziellen integrierten Konstruktoren erstellt wurden: Function (Funktionsobjektkonstruktor), Array (Array-Konstruktor), RegExp (Konstruktor für reguläre Ausdrücke), Math (Mathemodul), Date (Datumskonstruktor) (Container) usw. Diese Objekte sind ebenfalls Werte des Objekttyps Object. Ihre Unterschiede untereinander werden durch interne Eigenschaften verwaltet, die wir weiter unten besprechen.
Wörtlich
Für die Werte von drei Objekten: Objekt, Array und regulärer Ausdruck, haben sie abgekürzte Bezeichner namens: Objektinitialisierer, Array-Initialisierer und regulärer Ausdruck:
Beachten Sie, dass, wenn die oben genannten drei Objekte neuen Typen zugewiesen werden, die nachfolgende Implementierungssemantik entsprechend den neu zugewiesenen Typen verwendet wird. Beispielsweise wird dies in der aktuellen Implementierung von Rhino und der alten Version von SpiderMonkey 1.7 der Fall sein Das Objekt wird erfolgreich mit dem Konstruktor des neuen Schlüsselworts erstellt, aber in einigen Implementierungen (derzeit Spider/TraceMonkey) ändert sich die Semantik von Literalen nicht unbedingt, nachdem der Typ geändert wurde.
Literale regulärer Ausdrücke und RegExp-Objekte
Beachten Sie, dass in den folgenden beiden Beispielen die Semantik regulärer Ausdrücke in der dritten Ausgabe der Spezifikation äquivalent ist. Das Regexp-Literal existiert nur in einem Satz und wird in der Parsing-Phase erstellt, jedoch in dem vom RegExp-Konstruktor erstellten Da es sich um ein neues Objekt handelt, kann dies zu Problemen führen. Beispielsweise ist der Wert von lastIndex beim Testen falsch:
Assoziatives Array
In verschiedenen statischen Textdiskussionen werden JavaScript-Objekte (oft mit dem Objektinitialisierer {} erstellt) als Hash-Tabellen, Hash-Tabellen oder andere einfache Namen bezeichnet: Hash (ein Konzept in Ruby oder Perl), Management Array (ein Konzept in PHP) , Wörterbuch (ein Konzept in Python) usw.
Es gibt nur solche Begriffe, hauptsächlich weil ihre Strukturen ähnlich sind, das heißt, sie verwenden „Schlüssel-Wert“-Paare zum Speichern von Objekten, was vollständig mit der Datenstruktur übereinstimmt, die durch die Theorie des „assoziativen Arrays“ oder „Hash“ definiert wird Tisch". Darüber hinaus wird auf der Implementierungsebene normalerweise der abstrakte Datentyp der Hash-Tabelle verwendet.
Obwohl die Terminologie dieses Konzept beschreibt, ist es tatsächlich ein Fehler. Aus der Sicht von ECMAScript: ECMAScript hat nur ein Objekt und einen Typ und seine Untertypen, was sich nicht von der Speicherung von „Schlüssel-Wert“-Paaren unterscheidet Es gibt hierzu kein spezielles Konzept. Weil die internen Eigenschaften jedes Objekts als Schlüssel-Wert-Paare gespeichert werden können:
Da Objekte in ECMAScript auch leer sein können, ist das Konzept von „Hash“ auch hier falsch:
Bitte beachten Sie, dass der ES5-Standard es uns ermöglicht, Objekte ohne Prototypen zu erstellen (implementiert mit der Methode Object.create(null)). Aus dieser Perspektive können solche Objekte als Hash-Tabellen bezeichnet werden:
Aber selbst wenn man davon ausgeht, dass „Hash“ möglicherweise einen „Prototyp“ hat (z. B. eine Klasse, die Hash-Objekte in Ruby oder Python delegiert), ist diese Terminologie in ECMAScript falsch, da zwischen beiden eine Lücke besteht Darstellungen. Es gibt keinen semantischen Unterschied (d. h. Verwendung der Punktnotation a.b und a["b"]-Notation).
Das Konzept und die Semantik des „Eigenschaftsattributs“ in ECMAScript sind nicht von „Schlüssel“, Array-Index und Methode getrennt. Das Lesen und Schreiben der Eigenschaften aller Objekte muss hier denselben Regeln folgen: Überprüfen Sie die Prototypenkette.
Im folgenden Ruby-Beispiel können wir den semantischen Unterschied sehen:
Objektkonvertierung
Um ein Objekt in einen primitiven Wert umzuwandeln, können Sie die valueOf-Methode verwenden, wenn der Konstruktor der Funktion (für einige Typen) als Funktion aufgerufen wird, aber wenn das neue Schlüsselwort nicht verwendet wird Das Objekt wird in einen primitiven Wert umgewandelt, der dem impliziten valueOf-Methodenaufruf entspricht:
Der Standardwert von valueOf ändert sich je nach Typ des Objekts (sofern er nicht überschrieben wird), zum Beispiel: Object.prototype.valueOf() und berechnete Werte: Date.prototype .valueOf() gibt Datum und Uhrzeit zurück:
Die auf Object.prototype definierte toString-Methode hat eine besondere Bedeutung. Sie gibt den internen [[Class]]-Attributwert zurück, den wir weiter unten besprechen werden.
Im Vergleich zur Konvertierung in primitive Werte (ToPrimitive) gibt es für die Konvertierung von Werten in Objekttypen auch eine Konvertierungsspezifikation (ToObject).
Eine explizite Methode besteht darin, den integrierten Objektkonstruktor als Funktion zum Aufrufen von ToObject zu verwenden (ähnlich dem neuen Schlüsselwort):
Es gibt keine allgemeinen Regeln für den Aufruf integrierter Konstruktoren. Ob der neue Operator verwendet wird oder nicht, hängt vom Konstruktor ab. Array oder Funktion erzeugen beispielsweise das gleiche Ergebnis, wenn sie als Konstruktor mit dem neuen Operator oder einer einfachen Funktion verwendet werden, die den neuen Operator nicht verwendet:
Eigenschaften von Attributen
Alle Eigenschaften können viele Attribute haben.
1.{ReadOnly} – Ignorieren Sie den Schreibvorgang zum Zuweisen eines Werts zur Eigenschaft, aber die schreibgeschützte Eigenschaft kann durch das Verhalten der Hostumgebung geändert werden – das heißt, sie ist kein „konstanter Wert“;
2.{DontEnum} – Attribute können nicht durch die for..in-Schleife
aufgezählt werden
3.{DontDelete} – Das Verhalten des Löschoperators wird ignoriert (d. h. er kann nicht gelöscht werden);
4. {Internal} – Internes Attribut, kein Name (wird nur auf Implementierungsebene verwendet), auf solche Attribute kann in ECMAScript nicht zugegriffen werden.
Beachten Sie, dass in ES5 {ReadOnly}, {DontEnum} und {DontDelete} in [[Writable]], [[Enumerable]] und [[Configurable]] umbenannt werden, die manuell über Object.defineProperty oder ähnliches übergeben werden können Methoden zum Verwalten dieser Eigenschaften.
Interne Eigenschaften und Methoden
Objekte können auch interne Eigenschaften haben (Teil der Implementierungsebene), auf die ECMAScript-Programme nicht direkt zugreifen können (aber wie wir weiter unten sehen werden, ermöglichen einige Implementierungen den Zugriff auf einige dieser Eigenschaften). Der Zugriff auf diese Eigenschaften erfolgt über verschachtelte eckige Klammern [[ ]]. Schauen wir uns einige davon an. Die Beschreibung dieser Eigenschaften finden Sie in der Spezifikation.
Jedes Objekt sollte die folgenden internen Eigenschaften und Methoden implementieren:
1.[[Prototyp]] – der Prototyp des Objekts (wird weiter unten ausführlich vorgestellt)
2.[[Klasse]] – eine Darstellung eines Zeichenfolgenobjekts (z. B. Objektarray, Funktionsobjekt, Funktion usw.); wird zur Unterscheidung von Objekten verwendet
3.[[Get]] – Methode zum Abrufen des Attributwerts
4.[[Put]] – Methode zum Festlegen des Attributwerts
5.[[CanPut]] – Prüfen Sie, ob das Attribut beschreibbar ist
6.[[HasProperty]] – Prüfen Sie, ob das Objekt bereits über diese Eigenschaft verfügt
7.[[Löschen]] – Löschen Sie das Attribut aus dem Objekt
8.[[DefaultValue]] gibt den ursprünglichen Wert des Objekts zurück (bei Aufruf der valueOf-Methode lösen einige Objekte möglicherweise eine TypeError-Ausnahme aus).
Der Wert der internen Eigenschaft [[Class]] kann indirekt über die Methode Object.prototype.toString() abgerufen werden, die die folgende Zeichenfolge zurückgeben sollte: „[object „ [[Class]] „]“ . Zum Beispiel:
Konstrukteur
Wie oben erwähnt, werden Objekte in ECMAScript durch sogenannte Konstruktoren erstellt.
Konstruktor ist eine Funktion, die das neu erstellte Objekt erstellt und initialisiert.
Ein Konstruktor ist eine Funktion, die ein neu erstelltes Objekt erstellt und initialisiert.
Die Objekterstellung (Speicherzuweisung) erfolgt durch die interne Methode des Konstruktors [[Construct]]. Das Verhalten dieser internen Methode ist klar definiert und alle Konstruktoren verwenden diese Methode, um Speicher für neue Objekte zuzuweisen.
Die Initialisierung wird durch Aufrufen dieser Funktion auf und ab des neuen Objekts verwaltet, das für die interne Methode [[Call]] des Konstruktors verantwortlich ist.
Beachten Sie, dass auf Benutzercode nur während der Initialisierungsphase zugegriffen werden kann, obwohl wir während der Initialisierungsphase ein anderes Objekt zurückgeben können (wobei wir das in der ersten Phase erstellte tihs-Objekt ignorieren):
Bezugnehmend auf Kapitel 15 Funktion – Algorithmus zum Erstellen von Funktionen können wir sehen, dass die Funktion ein natives Objekt ist, einschließlich der Attribute [[Construct]] ] und [[Call]] ] sowie dem angezeigten Prototyp-Prototyp-Attribut – dem Zukunft Der Prototyp des Objekts (Hinweis: NativeObject ist eine Konvention für native Objekte und wird im folgenden Pseudocode verwendet).
[[Call]]] ist die Hauptmethode zur Unterscheidung von Objekten außer dem [[Class]]-Attribut (hier gleichbedeutend mit „Funktion“), daher wird das interne [[Call]]-Attribut des Objekts als a bezeichnet Funktion. Die Verwendung des Operators „typeof“ für ein solches Objekt gibt „Funktion“ zurück. Dies hängt jedoch hauptsächlich mit nativen Objekten zusammen. In einigen Fällen ist die Implementierung von typeof zum Abrufen des Werts unterschiedlich. Beispiel: Die Wirkung von window.alert (...) im IE:
Die interne Methode [[Construct]] wird durch die Verwendung des Konstruktors mit dem neuen Operator aktiviert. Wie bereits erwähnt, ist diese Methode für die Speicherzuweisung und Objekterstellung verantwortlich. Wenn keine Parameter vorhanden sind, können die Klammern zum Aufruf des Konstruktors auch weggelassen werden:
Lassen Sie uns den Objekterstellungsalgorithmus untersuchen.
Algorithmus zur Objekterstellung
Das Verhalten der internen Methode [[Construct]] kann wie folgt beschrieben werden:
Bitte beachten Sie zwei Hauptmerkmale:
1. Zunächst wird der Prototyp des neu erstellten Objekts aus dem Prototypattribut der Funktion zum aktuellen Zeitpunkt abgerufen (dies bedeutet, dass die Prototypen zweier erstellter Objekte, die von demselben Konstruktor erstellt wurden, unterschiedlich sein können, da das Prototypattribut von die Funktion kann auch unterschiedlich sein).
2. Zweitens, wie oben erwähnt, wenn [[Call]] ein Objekt zurückgibt, wenn das Objekt initialisiert wird, ist dies genau das Ergebnis, das für den gesamten neuen Operator verwendet wird:
Prototyp
Jedes Objekt hat einen Prototyp (mit Ausnahme einiger Systemobjekte). Die Prototypenkommunikation erfolgt über die interne, implizite und nicht direkt zugängliche Prototypeigenschaft [[Prototype]]. Der Prototyp kann ein Objekt oder ein Nullwert sein.
Eigenschaftskonstruktor
Im obigen Beispiel gibt es zwei wichtige Wissenspunkte. Der erste betrifft das Prototypattribut des Konstruktorattributs der Funktion. Wir wissen, dass das Konstruktorattribut auf das Prototypattribut der Funktion festgelegt ist Funktion während der Funktionserstellungsphase ist der Wert des Konstruktorattributs ein wichtiger Verweis auf die Funktion selbst:
Normalerweise liegt in diesem Fall ein Missverständnis vor: Die Konstruktor-Konstruktoreneigenschaft ist als Eigenschaft des neu erstellten Objekts selbst falsch, aber wie wir sehen können, gehört diese Eigenschaft zum Prototyp und wird durch Vererbung abgerufen.
Durch die Vererbung der Instanz des Konstruktorattributs können Sie indirekt einen Verweis auf das Prototypobjekt erhalten:
Bitte beachten Sie jedoch, dass die Konstruktor- und Prototypattribute der Funktion nach der Erstellung des Objekts neu definiert werden können. In diesem Fall verliert das Objekt den oben beschriebenen Mechanismus. Wenn Sie den Prototyp des Elements über das Prototypattribut der Funktion bearbeiten (ein neues Objekt hinzufügen oder ein vorhandenes Objekt ändern), werden die neu hinzugefügten Attribute auf der Instanz angezeigt.
Wenn wir jedoch die Prototypeigenschaft der Funktion vollständig ändern (indem wir ein neues Objekt zuweisen), geht der Verweis auf den ursprünglichen Konstruktor verloren, da das von uns erstellte Objekt die Konstruktoreigenschaft nicht enthält:
Beachten Sie, dass die Funktion {DontEnum} im Vergleich zum ursprünglichen verlorenen Prototyp zwar manuell wiederhergestellt wurde, die Funktion {DontEnum} jedoch nicht mehr verfügbar ist, was bedeutet, dass die for..in-Schleifenanweisung in A.prototype nicht unterstützt wird bietet in der 5. Ausgabe der Spezifikation die Möglichkeit, den aufzählbaren Zustand über das Attribut [[Enumerable]] zu steuern.
Expliziter Prototyp und implizite [[Prototyp]]-Attribute
Im Allgemeinen ist es falsch, den Prototyp eines Objekts explizit über das Prototypattribut der Funktion zu referenzieren. Es bezieht sich auf dasselbe Objekt, das [[Prototyp]]-Attribut des Objekts:
a.[[Prototyp]] ----> Prototyp <---- A.prototyp
Darüber hinaus wird der [[Prototype]]-Wert der Instanz tatsächlich aus dem Prototyp-Attribut des Konstruktors erhalten.
Die Übermittlung des Prototypattributs wirkt sich jedoch nicht auf den Prototyp des bereits erstellten Objekts aus (es wirkt sich nur aus, wenn sich das Prototypattribut des Konstruktors ändert, d. h. nur neu erstellte Objekte haben neue Prototypen). Bereits erstellte Objekte verfügen weiterhin über neue Prototypen, die auf den ursprünglichen alten Prototyp verweisen (dieser Prototyp kann nicht mehr geändert werden).
Zum Beispiel:
Die Hauptregel hier lautet: Der Prototyp eines Objekts wird erstellt, wenn das Objekt erstellt wird, und kann danach nicht mehr in ein neues Objekt geändert werden. Wenn es immer noch auf dasselbe Objekt verweist, kann es über den expliziten Prototyp referenziert werden des Konstruktors Nachdem das Objekt erstellt wurde, können nur die Eigenschaften des Prototyps hinzugefügt oder geändert werden.
Nicht standardmäßiges __proto__-Attribut
Einige Implementierungen, wie z. B. SpiderMonkey, stellen jedoch das nicht standardmäßige explizite Attribut __proto__ bereit, um auf den Prototyp des Objekts zu verweisen:
Objekt unabhängig vom Konstruktor
Da der Prototyp der Instanz unabhängig vom Konstruktor und dem Prototypattribut des Konstruktors ist, kann der Konstruktor nach Abschluss seiner Hauptarbeit (Erstellen des Objekts) gelöscht werden. Prototypobjekte bleiben bestehen, indem sie auf das Attribut [[Prototype]] verweisen:
Eigenschaften des Instanzoperators
Wir zeigen den Referenzprototyp über das Prototypattribut des Konstruktors an, das mit dem Instanzoperator verknüpft ist. Dieser Operator arbeitet mit der Prototypenkette, nicht mit dem Konstruktor. Vor diesem Hintergrund kommt es häufig zu Missverständnissen bei der Erkennung von Objekten:
Sehen wir uns dieses Beispiel an:
Andererseits kann ein Objekt von einem Konstruktor erstellt werden, aber wenn das [[Prototype]]-Attribut des Objekts und der Wert des Prototyp-Attributs des Konstruktors auf denselben Wert gesetzt sind, gibt „instanceof“ „true“ zurück wenn aktiviert:
Prototypen können Methoden speichern und Eigenschaften teilen
Prototypen werden in den meisten Programmen verwendet, um Objektmethoden, Standardzustände und gemeinsame Objekteigenschaften zu speichern.
Tatsächlich können Objekte ihren eigenen Zustand haben, aber die Methoden sind normalerweise dieselben. Daher werden Methoden zur Speicheroptimierung üblicherweise in Prototypen definiert. Dies bedeutet, dass alle von diesem Konstruktor erstellten Instanzen diese Methode gemeinsam nutzen können.
Attribute lesen und schreiben
Wie bereits erwähnt, erfolgt das Lesen und Schreiben von Eigenschaftswerten über die internen Methoden [[Get]] und [[Put]]. Diese internen Methoden werden durch Eigenschaftszugriffsfunktionen aktiviert: Punktnotation oder Indexnotation:
[[Get]]-Methode
[[Get]] fragt auch Eigenschaften aus der Prototypenkette ab, sodass über das Objekt auch auf Eigenschaften im Prototypen zugegriffen werden kann.
O.[[Get]](P):
Hinweis: Der in-Operator kann auch für die Suche nach Eigenschaften verantwortlich sein (er sucht auch nach der Prototypenkette):
[[Put]]-Methode
Die[[Put]]-Methode kann die Eigenschaften des Objekts selbst erstellen und aktualisieren und die gleichnamigen Eigenschaften im Prototyp maskieren.
O.[[Put]](P, V):
// Beispielsweise ist die Attributlänge schreibgeschützt. Versuchen wir, die Länge zu maskieren
Funktion SuperString() {
/* nichts */
}
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, die Länge von „abc“
// Versuch,
zu maskieren
foo.length = 5;
console.log(foo.length); // Immer noch 3
Eigenschafts-Accessor
Die internen Methoden [[Get]] und [[Put]] werden durch Punktnotation oder Indizierung in ECMAScript aktiviert. Wenn der Attributbezeichner ein gültiger Name ist, kann über „.“ darauf zugegriffen werden, und die Indizierungspartei wird dynamisch ausgeführt definierte Namen.
Hier gibt es eine sehr wichtige Funktion: Eigenschaftszugriffsfunktionen verwenden immer die ToObject-Spezifikation, um den Wert links von „.“ zu behandeln. Diese implizite Konvertierung hängt mit dem Sprichwort „Alles in JavaScript ist ein Objekt“ zusammen (allerdings sind, wie wir bereits wissen, nicht alle Werte in JavaScript Objekte).
Wenn der Attribut-Accessor verwendet wird, um auf den Originalwert zuzugreifen, wird der Originalwert vor dem Zugriff vom Objekt umschlossen (einschließlich des Originalwerts), und dann wird auf das Attribut über das umschlossene Objekt zugegriffen, nachdem auf das Attribut zugegriffen wurde , das umschlossene Objekt wird gelöscht.
Zum Beispiel:
Die Antwort ist einfach:
Zunächst einmal handelt es sich, wie gesagt, nach der Verwendung des Eigenschafts-Accessors nicht mehr um den ursprünglichen Wert, sondern um ein umschlossenes Zwischenobjekt (das gesamte Beispiel verwendet new Number(a)), und die toString-Methode wird dabei übergeben Zeit Gefunden in der Prototypenkette:
Wir sehen, dass in Schritt 3 das umschlossene Objekt gelöscht wird und die neu erstellte Eigenschaftenseite gelöscht wird – wodurch das umschließende Objekt selbst gelöscht wird.
Wenn Sie [[Get]] verwenden, um den Testwert abzurufen, wird das Verpackungsobjekt erneut erstellt, aber dieses Mal verfügt das umhüllte Objekt nicht mehr über das Testattribut, sodass undefiniert zurückgegeben wird:
Erben
Wir wissen, dass ECMAScript eine prototypbasierte delegierte Vererbung verwendet. Ketten und Prototypen wurden bereits in der Prototypenkette erwähnt. Tatsächlich sind die gesamte Implementierung der Delegation sowie die Suche und Analyse der Prototypenkette in der Methode [[Get]] zusammengefasst.
Wenn Sie die Methode [[Get]] vollständig verstehen, ist die Frage der Vererbung in JavaScript selbsterklärend.
Wenn ich in Foren oft über Vererbung spreche, verwende ich immer eine Codezeile, um sie anzuzeigen. Tatsächlich müssen wir keine Objekte oder Funktionen erstellen, da die Sprache bereits auf Vererbung basiert lautet wie folgt:
1. Erstellen Sie zunächst ein Verpackungsobjekt aus dem ursprünglichen Wert 1 bis zur neuen Zahl (1)
2. Dann wird die toString-Methode von diesem Verpackungsobjekt
Warum wird es vererbt? Da Objekte in ECMAScript ihre eigenen Eigenschaften haben können, verfügt das Wrapper-Objekt in diesem Fall nicht über eine toString-Methode. Es erbt also vom Prinzip Number.prototype.
Beachten Sie, dass es eine Feinheit gibt: Die beiden Punkte im obigen Beispiel sind kein Fehler. Der erste Punkt stellt den Dezimalteil dar und der zweite Punkt ist ein Attribut-Accessor:
Prototypenkette
Lassen Sie uns zeigen, wie Sie eine Prototypenkette für ein benutzerdefiniertes Objekt erstellen. Es ist ganz einfach:
Diese Methode weist zwei Merkmale auf:
Zuerst enthält B.prototype das x-Attribut. Auf den ersten Blick mag das nicht richtig erscheinen, Sie könnten denken, dass die x-Eigenschaft in A definiert ist und dass der B-Konstruktor sie auch erwartet. Obwohl die prototypische Vererbung unter normalen Umständen kein Problem darstellt, benötigt der B-Konstruktor manchmal das x-Attribut nicht. Im Vergleich zur klassenbasierten Vererbung werden alle Attribute in abgeleitete Unterklassen kopiert.
Wenn jedoch die Notwendigkeit besteht (zur Simulation der klassenbasierten Vererbung), das x-Attribut dem vom B-Konstruktor erstellten Objekt zuzuweisen, gibt es einige Möglichkeiten, von denen wir eine später zeigen werden.
Zweitens ist dies kein Feature, sondern ein Nachteil – wenn der Unterklassenprototyp erstellt wird, wird auch der Code des Konstruktors ausgeführt, und wir können sehen, dass die Meldung „A.[[Call]] aktiviert“ zweimal angezeigt wird - Wenn Sie den A-Konstruktor verwenden, um ein Objekt zu erstellen und es der B.prototype-Eigenschaft zuzuweisen, ist die andere Szene die, in der sich ein Objekt selbst erstellt!
Das folgende Beispiel ist kritischer, die vom Konstruktor der übergeordneten Klasse ausgelöste Ausnahme: Möglicherweise muss sie überprüft werden, wenn das eigentliche Objekt erstellt wird, aber offensichtlich ist dies derselbe Fall, wenn diese übergeordneten Objekte als verwendet werden Prototypen Irgendetwas wird schiefgehen.
Darüber hinaus ist es auch ein Nachteil, zu viel Code im Konstruktor der übergeordneten Klasse zu haben.
Um diese „Funktionen“ und Probleme zu lösen, verwenden Programmierer das Standardmuster von Prototypenketten (siehe unten). Der Hauptzweck besteht darin, die Erstellung von Konstruktoren in die Mitte zu packen. Die Ketten dieser Wrapper-Konstruktoren enthalten die erforderlichen Prototypen.