einführen
Dieses Kapitel ist das zweite Kapitel über die objektorientierte Implementierung von ECMAScript. Im ersten Kapitel diskutieren wir den Vergleich zwischen Einführung und CemaScript. Wenn Sie das erste Kapitel nicht gelesen haben, bevor Sie mit diesem Kapitel fortfahren, empfehle ich dringend, dass Sie das erste Kapitel zuerst lesen, da dieses Kapitel zu lang ist (Seite 35).
Original Englisch Text: http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
Hinweis: Da der Artikel zu lang ist, sind Fehler unvermeidlich und sie werden ständig korrigiert.
In der Einführung erstrecken wir uns auf ECMascript. Wenn wir nun wissen, dass es sich um eine OOP -Implementierung handelt, definieren wir sie genau:
Die Codekopie lautet wie folgt:
ECMAScript ist eine objektorientierte Programmiersprache, die die Delegierende Vererbung basierend auf Prototypen unterstützt.
ECMAScript ist eine objektorientierte Sprache, die prototypbasierte delegierte Vererbung unterstützt.
Wir werden die grundlegendsten Datentypen analysieren. Das erste, was wir verstehen müssen, ist, dass ECMascript primitive Werte und Objekte verwendet, um Entitäten zu unterscheiden. Daher ist das in einigen Artikeln erwähnte "In JavaScript, alles ein Objekt" erwähnt (nicht vollständig korrekt), und der primitive Wert sind einige Datentypen, die wir hier diskutieren werden.
Datentyp
Obwohl ECMascript eine dynamische Sprache vom schwachen Typ ist, die Typen dynamisch konvertieren kann, verfügt sie über Datentypen. Mit anderen Worten muss ein Objekt zu einem realen Typ gehören.
In der Standardspezifikation sind 9 Datentypen definiert, aber nur 6 sind in ECMascript -Programmen direkt zugänglich. Sie sind: undefiniert, Null, Boolean, String, Nummer und Objekt.
Auf die anderen drei Typen können nur auf der Implementierungsebene zugegriffen werden (diese Typen können nicht von ECMAScript -Objekten verwendet werden) und werden für Spezifikationen verwendet, um ein gewisses operatives Verhalten zu erklären und Zwischenwerte zu speichern. Diese 3 Typen sind: Referenz, Liste und Fertigstellung.
Daher wird Referenz verwendet, um Operatoren wie Löschen, Typen und diese zu erklären und ein Basisobjekt und einen Attributnamen enthält. Die Liste beschreibt das Verhalten der Parameterliste (wenn neue Ausdrücke und Funktionsaufrufe); Die Fertigstellung wird verwendet, um die Verhaltensunterbrechung zu erklären, fortzusetzen, zurückzukehren und Aussagen zu werfen.
Primitiver Werttyp
Rückblickend auf die Datentypen, die in ECMascript -Programmen in 6 verwendet werden, sind die ersten 5 primitiven Werttypen, einschließlich undefinierter, Null, Boolean, String, Anzahl und Objekt.
Beispiel des ursprünglichen Werttyps:
Die Codekopie lautet wie folgt:
var a = undefiniert;
var b = null;
var c = true;
var d = 'test';
var e = 10;
Diese Werte werden direkt in der unteren Schicht implementiert, sie sind keine Objekte, daher gibt es keinen Prototyp, keinen Konstruktor.
Onkel Anmerkung: Obwohl diese nativen Werte denjenigen ähnlich sind, die wir normalerweise verwenden (Boolean, String, Zahl, Objekt), aber nicht dasselbe sind. Daher sind die Ergebnisse von TypeOF (TRUE) und TypoOF (boolean) unterschiedlich, da das Ergebnis von TypeOF (boolean) eine Funktion ist, sodass die Funktionen boolean, Zeichenfolge und Nummer Prototypen haben (das Kapitel "Lesen- und Schreibattribute" wird ebenfalls erwähnt).
Wenn Sie wissen möchten, welche Art von Daten am besten ist, verwenden Sie am besten Typeof. Es gibt ein Beispiel, das notiert werden muss. Wenn Sie Typen verwenden, um die Art der Null zu beurteilen, ist das Ergebnis Objekt. Warum? Weil die Art der Null als Null definiert ist.
Die Codekopie lautet wie folgt:
alarm (typeof null); // "Objekt"
Der Grund für das Anzeigen von "Objekt" liegt darin, dass die Spezifikation Folgendes vorsieht: Rückgabe "Objekt" für den Typ -von -String -Wert des Nullwerts.
Die Spezifikation stellt sich nicht vor, dies zu erklären, aber Brendan Eich (Erfinder von JavaScript) bemerkte, dass Null hauptsächlich für Objekte verwendet wird, die relativ zu Undefined erscheinen, z. B. ein Objekt auf eine Nullreferenz. Einige Dokumente haben jedoch einige nervig, um es einem Fehler zuzuschreiben und den Fehler in die Fehlerliste zu setzen, die Brendan Eich ebenfalls an der Diskussion beteiligt hat. Das Ergebnis ist, dass Sie, wenn Sie es loslassen, das Ergebnis des Typs von Null auf Objekt festlegen sollten (obwohl der 262-3-Standard den Nulltyp als Null definiert und der 262-5 den Standard als Objekt in Null geändert hat).
Objekttyp
Als nächstes ist der Objekttyp (nicht mit dem Objektkonstruktor zu verwechseln, jetzt wird nur der abstrakte Typ diskutiert) der einzige Datentyp, der das ECMAScript -Objekt beschreibt.
Objekt ist eine ungeordnete Sammlung von Schlüsselwertpaaren.
Objekt ist eine ungeordnete Sammlung mit Schlüsselwertpaaren
Der Schlüsselwert eines Objekts wird als Eigenschaft bezeichnet, und die Eigenschaft ist ein Container des ursprünglichen Wertes und anderer Objekte. Wenn der Wert einer Eigenschaft eine Funktion ist, nennen wir sie eine Methode.
Zum Beispiel:
Die Codekopie lautet wie folgt:
var x = {// Das Objekt "x" hat 3 Eigenschaften: a, b, c
A: 10, // Originalwert
B: {z: 100}, // Das Objekt "B" hat ein Attribut Z
C: function () {// Funktion (Methode)
Alert ('Methode X.C');
}
};
Alarm (xa); // 10
Alarm (xb); // [Objektobjekt]
Alarm (xbz); // 100
xc (); // 'Methode X.C'
Dynamisch
Wie wir in Kapitel 17 betonten, sind Objekte in ES vollständig dynamisch. Dies bedeutet, dass wir beim Ausführen des Programms die Eigenschaften des Objekts nach Belieben hinzufügen, ändern oder löschen können.
Zum Beispiel:
Die Codekopie lautet wie folgt:
var foo = {x: 10};
// neue Attribute hinzufügen
foo.y = 20;
console.log (foo); // {x: 10, y: 20}
// den Eigenschaftswert in eine Funktion ändern
foo.x = function () {
console.log ('foo.x');
};
foo.x (); // 'foo.x'
// Attribute löschen
Foo.x löschen;
console.log (foo); // {y: 20}
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 Attributmerkmalen erklären.
Darüber hinaus sieht die ES5 -Spezifikation fest, dass statische Objekte neue Attribute nicht erweitern können und ihre Eigenschaftsseiten nicht gelöscht oder geändert werden können. Es handelt sich um sogenannte gefrorene Objekte, die durch Anwenden des Objekts erhalten werden können.
Die Codekopie lautet wie folgt:
var foo = {x: 10};
// das Objekt einfrieren
Object.Freeze (foo);
console.log (Object.isfordrozen (foo)); // WAHR
// kann nicht geändert werden
foo.x = 100;
// kann nicht erweitern
foo.y = 200;
// kann nicht gelöscht werden
Foo.x löschen;
console.log (foo); // {x: 10}
In der ES5 -Spezifikation wird auch das Objekt.
Die Codekopie lautet wie folgt:
var foo = {x: 10};
Object.DefineProperty (foo, "y", {
Wert: 20,
Beschreibbar: falsch, // nur lesen
Konfigurierbar: Falsch // nicht konfigurierbar
});
// kann nicht geändert werden
foo.y = 200;
// kann nicht gelöscht werden
Foo.y löschen; // FALSCH
// Präventions- und Kontrollweiterung
Object.Preventextensions (Foo);
console.log (Object.ISEXTLINDIBLE (foo)); // FALSCH
// kann keine neuen Attribute hinzufügen
foo.z = 30;
console.log (foo); {x: 10, y: 20}
Integrierte Objekte, native Objekte und Host-Objekte
Es ist zu beachten, dass die Spezifikation auch diese integrierten Objekte, Elementobjekte und Hostobjekte unterscheidet.
Eingebaute Objekte und Elementobjekte werden durch die ECMascript-Spezifikation definiert und implementiert, und der Unterschied zwischen beiden ist trivial. Alle ECMAScript-Implementierungsobjekte sind native Objekte (von denen einige integrierte Objekte sind, einige werden erstellt, wenn das Programm ausgeführt wird, z. B. benutzerdefinierte Objekte). Ein integriertes Objekt ist eine Untergruppe des nativen Objekts und ist in ECMASScript integriert, bevor das Programm startet (z. B. ParseInt, Übereinstimmung usw.). Alle Host -Objekte werden von der Hostumgebung bereitgestellt, normalerweise ein Browser, und können beispielsweise Fenster, Alarm usw. enthalten.
Beachten Sie, dass das Host -Objekt von ES selbst implementiert und vollständig der normativen Semantik entspricht. In dieser Hinsicht können sie als "native Host" -Objekte bezeichnet werden (so bald wie möglich), aber die Norm definiert das Konzept von "nativen Host" -Objekten nicht.
Boolesche, Zeichenfolge und Zahlenobjekte
Darüber hinaus definiert die Spezifikation auch einige native Spezialverpackungsklassen, diese Objekte sind:
1. Boolesche Objekt
2. String -Objekt
3.. Digitale Objekte
Diese Objekte werden vom entsprechenden integrierten Konstruktor erstellt und enthalten native Werte als interne Eigenschaften. Diese Objekte können konvertiert werden, um den ursprünglichen Wert zu speichern und umgekehrt.
Die Codekopie lautet wie folgt:
var c = neuer boolean (true);
var d = neuer String ('Test');
var e = neue Zahl (10);
// in den ursprünglichen Wert konvertieren
// Funktionen ohne neues Schlüsselwort verwenden
с = boolean (c);
d = String (d);
e = Nummer (e);
// Um das Objekt umzuwandeln
с = Objekt (c);
d = Objekt (d);
e = Objekt (e);
Darüber hinaus gibt es Objekte, die von speziellen integrierten Konstruktoren erstellt wurden: Funktion (Funktionsobjektkonstruktor), Array (Array Constructor), Regexp (regulärer Expressionskonstruktor), Mathematik (Mathematikmodul), Datum (Datumskonstruktor) usw. Diese Objekte sind auch Werte von Objekttypen. Ihre Unterschiede werden durch interne Eigenschaften verwaltet. Wir werden diese Dinge unten diskutieren.
Wörtlich
Für die Werte von drei Objekten: Objekt, Array und regulärer Ausdruck haben sie abgekürzte Kennungen, die als Objektinitializer, Array Initializer und regulärer Expression -Initializer bezeichnet werden:
Die Codekopie lautet wie folgt:
// Äquivalent zu Neuarray (1, 2, 3);
// oder array = new Array ();
// Array [0] = 1;
// Array [1] = 2;
// Array [2] = 3;
var Array = [1, 2, 3];
// Äquivalent zu
// var Object = new Object ();
// Objekt.A = 1;
// Object.b = 2;
// Object.c = 3;
var Object = {a: 1, b: 2, c: 3};
// Äquivalent zu neuem Regexp ("^// D+$", "G")
var re =/^/d+$/g;
Beachten Sie, dass die nachfolgende Implementierungssemantik gemäß dem neuen Typ verwendet wird, wenn die obigen drei Objekte zu einem neuen Typ zugewiesen werden. Zum Beispiel werden im aktuellen Nashorn und der alten Version von Spidermonkey 1.7 die Objekte erfolgreich mit dem Konstruktor des neuen Schlüsselworts erstellt, aber die Semantik einiger Implementierungen (aktuelle Spinnen/Tracemonkey) -Plänen ändert sich möglicherweise nicht, nachdem sich der Typ geändert hat.
Die Codekopie lautet wie folgt:
var getClass = Object.Prototype.toString;
Objekt = Zahl;
var foo = neues Objekt;
Alert ([foo, getClass.call (foo)]); // 0, "[Objektnummer]"
var bar = {};
// Rhino, Spidermonkey 1.7- 0, "[Objektnummer]"
// Andere: immer noch "[Objekt]", "[Objektobjekt]"
Alert ([Bar, getClass.call (bar)]);
// Array hat den gleichen Effekt
Array = Nummer;
foo = new Array;
Alert ([foo, getClass.call (foo)]); // 0, "[Objektnummer]"
bar = [];
// Rhino, Spidermonkey 1.7- 0, "[Objektnummer]"
// Andere: still "", "[Objektobjekt]"
Alert ([Bar, getClass.call (bar)]);
// Aber für Regexp werden die Semantik der Literale nicht geändert. Semantik des Literales
// wird in allen getesteten Implementierungen nicht geändert
Regexp = nummer;
foo = new regexp;
Alert ([foo, getClass.call (foo)]); // 0, "[Objektnummer]"
bar = /(?!) /g;
Alert ([Bar, getClass.call (bar)]); ///(?!)/g, "[Objekt regexp]"
Regex -Literale und Regexp -Objekte
Beachten Sie, dass in den folgenden zwei Beispielen in der Spezifikation der dritten Ausgabe die Semantik der regulären Ausdrücke gleichwertig sind. Die Regexp -Literale existieren nur in einem Satz und werden in der Parsenstufe erstellt. Der Regexp -Konstruktor erstellt jedoch ein neues Objekt, sodass dies einige Probleme verursachen kann, z.
Die Codekopie lautet wie folgt:
für (var k = 0; k <4; k ++) {
var re = /ecma /g;
Alert (re.lastindex); // 0, 4, 0, 4
Alert (re.Test ("ecmascript")); // wahr, falsch, wahr, falsch
}
// Vergleichen
für (var k = 0; k <4; k ++) {
var re = new regexp ("ecma", "g");
Alert (re.lastindex); // 0, 0, 0, 0
Alert (re.Test ("ecmascript")); // wahr, wahr, wahr, wahr
}
Hinweis: Diese Probleme wurden jedoch in der ES -Spezifikation in der 5. Ausgabe korrigiert. Unabhängig davon, ob sie auf Literalen oder Konstruktoren basieren, erstellen sie neue Objekte.
Assoziatives Array
Verschiedene statische Diskussionen über Text, JavaScript -Objekte (oft mit Objektinitializer {}) werden als Hash -Tabellen und Hash -Tabellen oder andere einfache Titel bezeichnet: Hash (Konzepte in Ruby oder Perl), Management -Arrays (Konzepte in PHP), Wörterbücher (Konzepte in Python) usw.
Es gibt nur solche Begriffe, vor allem, weil ihre Strukturen ähnlich sind, dh unter Verwendung von "Schlüsselwert" -Paaren, um Objekte zu speichern, die der durch die Theorie "Assoziationsarray" oder "Hash-Tabelle" definierten Datenstruktur vollständig entsprechen. Darüber hinaus werden abstrakte Datentypen der Hash -Tabelle normalerweise auf Implementierungsebene verwendet.
Obwohl das Konzept in Bezug auf Begriffe beschrieben wird, ist dies tatsächlich ein Fehler. Aus der Perspektive von ECMAScript: ECMascript hat nur ein Objekt, Typ und seinen Subtyp, was sich nicht von "Schlüsselwert" bis zum Speicher unterscheidet. Daher gibt es kein spezielles Konzept dazu. Da die internen Eigenschaften eines jeden Objekts als Schlüsselwertpaare gespeichert werden können:
Die Codekopie lautet wie folgt:
var a = {x: 10};
a ['y'] = 20;
AZ = 30;
var b = neue Zahl (1);
BX = 10;
durch = 20;
B ['z'] = 30;
var c = neue Funktion ('');
cx = 10;
cy = 20;
c ['z'] = 30;
// usw., Subtyp eines beliebigen Objekts "Subtyp"
Da Objekte in ECMascript leer werden können, ist das Konzept von "Hash" auch hier falsch:
Die Codekopie lautet wie folgt:
Object.Prototype.x = 10;
var a = {}; // leere "Hash" erstellen
alarm (a ["x"]); // 10, aber nicht leer
Alarm (A.Tostring); // Funktion
a ["y"] = 20; // Fügen Sie "Hash" neues Schlüsselwertpaar hinzu.
alarm (a ["y"]); // 20
Object.Prototype.y = 20; // Prototyp -Attribute hinzufügen
löschen a ["y"]; // Löschen
alarm (a ["y"]); // Aber hier sind der Schlüssel und der Wert immer noch 20 wert
Bitte beachten Sie, dass der ES5 -Standard es uns ermöglicht, nichttype Objekte zu erstellen (mit der Methode (Create) mit der Object.Create (NULL) implementiert). Aus dieser Perspektive können solche Objekte als Hash -Tabellen bezeichnet werden:
Die Codekopie lautet wie folgt:
var ahashtable = object.create (null);
console.log (ahashtable.toString); // undefiniert
Darüber hinaus haben einige Eigenschaften spezifische Getter/Setter -Methoden, sodass dies auch zu Verwirrung dieses Konzepts führen kann:
Die Codekopie lautet wie folgt:
var a = new String ("foo");
a ['Länge'] = 10;
Alarm (a ['Länge']); // 3
Selbst wenn der "Hash" einen "Prototyp" hat (z. B. eine Klasse, die Hash -Objekte in Ruby oder Python delegiert), ist dieser Begriff in ECMascript nicht korrekt, da es keinen semantischen Unterschied zwischen den beiden Notationen gibt (d. H. Die Punktnotation AB und A ["B"] Notation).
Das Konzept des "Eigenschaftsattributs" in ECMascript ist nicht semantisch von "Schlüssel", Array -Index und Methoden getrennt. Hier müssen alle Attributlesen und Schreiben von Objekten einer einheitlichen Regel folgen: Überprüfen Sie die Prototyp -Kette.
Im folgenden Ruby -Beispiel können wir den semantischen Unterschied sehen:
Die Codekopie lautet wie folgt:
a = {}
A.CLASS # Hash
A.Length # 0
# neues "Schlüsselwert" -Paar
a ['Länge'] = 10;
# Semantisch das Attribut oder die Methode, auf die mit einem Punkt zugegriffen wird, kein Schlüssel
A.Length # 1
# Der Indexer greift auf den Schlüssel im Hash zu
a ['Länge'] # 10
# Es ähnelt dem dynamischen Deklarieren einer Hash -Klasse für ein vorhandenes Objekt
# Dann deklarieren Sie das neue Attribut oder die neue Methode
Klasse Hash
def z
100
Ende
Ende
# Neue Eigenschaften sind zugänglich
AZ # 100
# Aber nicht "Schlüssel"
a ['z'] # nil
Der ECMA-262-3-Standard definiert das Konzept von "Hash" (und ähnlich) nicht. Wenn es jedoch eine solche Strukturtheorie gibt, kann das nach diesem benannte Objekt.
Objektumwandlung
Um ein Objekt in einen primitiven Wert umzuwandeln, können Sie die ValueOF -Methode verwenden. Wie gesagt, wenn der Konstruktor der Funktion als Funktion aufgerufen wird (für einige Typen), aber wenn Sie das neue Schlüsselwort nicht verwenden, konvertieren Sie das Objekt in den ursprünglichen Wert, es entspricht einem impliziten Wert von Methodenaufruf:
Die Codekopie lautet wie folgt:
var a = neue Zahl (1);
var primitivea = nummer (a); // impliziter "Wert von" Aufruf
var alsoprimitivea = a. -valueof (); // explizite Anruf
Alarm([
Typeof a, // "Objekt"
Art der Primitiven, // "Nummer"
Typof alsoprimitivea // "Nummer"
]);
Mit dieser Methode können Objekte an verschiedenen Operationen teilnehmen, wie z. B.:
Die Codekopie lautet wie folgt:
var a = neue Zahl (1);
var b = neue Zahl (2);
Alarm (a + b); // 3
// sogar
var c = {
x: 10,
y: 20,
valueof: function () {
zurück this.x + this.y;
}
};
var d = {
x: 30,
Y: 40,
// die gleiche Funktion wie Cs Wert von
Wert von: c.Valueof
};
Alarm (c + d); // 100
Der Standardwert des Wertes von Wert ändert sich entsprechend dem Typ des Objekts (falls nicht überschrieben). Für einige Objekte wird dies zurückgegeben - zum Beispiel: Object.Prototype.ValueOf () und auch der berechnete Wert: Datum.Prototype.ValueOf () Rückgabe Datum und Uhrzeit:
Die Codekopie lautet wie folgt:
var a = {};
alert (a.Valueof () === a); // true, "valueof" gibt dies zurück
var d = neues Datum ();
Alarm (d.Valueof ()); // Zeit
alert (d. -valueof () === D.GetTime ()); // WAHR
Darüber hinaus hat das Objekt eine primitivere Darstellung - Zeichenfolgeanzeige. Diese ToString -Methode ist zuverlässig und wird in einigen Vorgängen automatisch verwendet:
Die Codekopie lautet wie folgt:
var a = {
valueof: function () {
Rückkehr 100;
},
toString: function () {
zurück '__Test';
}
};
// In diesem Vorgang wird die TOString -Methode automatisch aufgerufen
Alarm (a); // "__prüfen"
// Aber hier wird die ValueOf () -Methode aufgerufen
Alarm (a + 10); // 110
// Sobald der Wert jedoch gelöscht ist
// Das ToString kann automatisch wieder aufgerufen werden
löschen a.Valueof;
Alarm (a + 10); // "_test10"
Die auf Object definierte ToString -Methode.
Im Vergleich zur Konvertierung in Originalwerte (TopRimitive) gibt es auch eine Konvertierungsspezifikation (toobject), um Werte in Objekttypen umzuwandeln.
Eine explizite Methode besteht darin, den integrierten Objektkonstruktor als Funktion zum Aufrufen von Toobject zu verwenden (so etwas wie das neue Schlüsselwort ist in Ordnung):
Die Codekopie lautet wie folgt:
var n = Objekt (1); // [Objektnummer]
var S = Objekt ('Test'); // [Objektstring]
// Einige Ähnlichkeiten sind auch mit dem neuen Bediener möglich
var b = neues Objekt (wahr); // [Objekt Boolean]
// Wenn Sie das neue Objekt Parameter anwenden, erstellen Sie ein einfaches Objekt
var o = neues Objekt (); // [Objektobjekt]
// Wenn der Parameter ein vorhandenes Objekt ist
// Das Ergebnis der Schöpfung besteht darin, das Objekt einfach zurückzugeben
var a = [];
alarm (a === neues Objekt (a)); // WAHR
alarm (a === Objekt (a)); // WAHR
In Bezug auf die aufgerufenen integrierten Konstruktoren gibt es je nach Konstruktor keine gemeinsamen Regeln für die Verwendung oder nicht, um den neuen Bediener anzuwenden. Beispielsweise verwendet Array oder Funktion einen Konstruktor des neuen Bedieners oder eine einfache Funktion, bei der der neue Bediener nicht das gleiche Ergebnis erzeugt:
Die Codekopie lautet wie folgt:
var a = Array (1, 2, 3); // [Objektarray]
var b = Neuarray (1, 2, 3); // [Objektarray]
var c = [1, 2, 3]; // [Objektarray]
var d = function (''); // [Objektfunktion]
var e = neue Funktion (''); // [Objektfunktion]
Wenn einige Operatoren verwendet werden, gibt es auch einige Anzeigen und implizite Conversions:
Die Codekopie lautet wie folgt:
var a = 1;
var b = 2;
// implizit
var c = a + b; // 3, Nummer
var d = a + b + '5' // "35", Zeichenfolge
// explizit
var e = '10'; // "10", String
var f = +e; // 10, Nummer
var g = parsesint (e, 10); // 10, Nummer
// usw
Eigenschaften von Attributen
Alle Eigenschaften können viele Attribute haben.
1. {Readonly} - Ignorieren Sie die Schreiboperation von Werten zu Attributen, aber schreibgeschützte Attribute können durch das Verhalten des Host -Umgebungsverhaltens geändert werden - dh keine "konstanten Werte";
2. {Dontenum}-Attribut kann nicht von für ... in Schleife aufgezählt werden
3. {dontdelete}-Das Verhalten des löschlichen Operators wird ignoriert (dh es kann nicht gelöscht werden);
4. {Internal} - Interne Attribut ohne einen Namen (nur auf der Implementierungsebene verwendet) können solche Attribute in ECMascript nicht zugegriffen werden.
Beachten Sie, dass in ES5 {Readonly}, {Dontenum} und {dontdelete} in [[Schreibbar]], [[aufzählbare]] und [Konfigurierbar]] umbenannt werden und diese Eigenschaften manuell über Objekt verwaltet werden können. DefineProperty oder ähnliche Methoden.
Die Codekopie lautet wie folgt:
var foo = {};
Object.DefineProperty (foo, "x", {
Wert: 10,
Schreibbar: wahr, // d. H. {readonly} = false
Aufzählbar: Falsch, // d. H. {Dontenum} = true
Konfigurierbar: true // d. H. {dontdelete} = false
});
console.log (foo.x); // 10
// Erhalten Sie die Attribute über die Beschreibung
var wasc = Object.getownPropertyDescriptor (foo, "x");
console.log (desc.Enumerable); // FALSCH
console.log (Desc.Wrable); // WAHR
// usw
Interne Eigenschaften und Methoden
Objekte können auch interne Eigenschaften (Teil der Implementierungsstufe) haben, und ECMascript -Programme können nicht direkt zugreifen (wir werden jedoch nachstehend angezeigt, dass einige Implementierungen Zugriff auf einige solche Eigenschaften ermöglichen). Diese Eigenschaften werden durch verschachtelte Klammern [[]] zugegriffen. Schauen wir uns einige dieser Eigenschaften 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 (der im Folgenden im Detail eingeführt wird)
2. [[Klasse]] - Eine Darstellung eines String -Objekts (z. B. Objektarray, Funktionsobjekt, Funktion usw.); Wird verwendet, um Objekte zu unterscheiden
3.. [[GET]]-Die Methode, um Attributwerte zu erhalten
4. [[Put]]-Die Methode zum Einstellen von Attributwerten
5. [[Canput]] - Überprüfen Sie, ob das Attribut beschreibbar ist
6. [[HasProperty]]-Überprüfen Sie, ob das Objekt bereits über diese Eigenschaft verfügt
7. [[[Delete]] - Löschen Sie diese Eigenschaft aus dem Objekt
8. [[defaultValue]] Gibt den ursprünglichen Wert des Objekts für (Aufrufen der ValueOF -Methode zurück, und einige Objekte können eine TypeERRor -Ausnahme auslegen).
Der Wert der internen Eigenschaft [[Klasse]] kann indirekt über die Methode des Objekts erhalten werden. Zum Beispiel:
Die Codekopie lautet wie folgt:
var getClass = Object.Prototype.toString;
getClass.call ({}); // [Objektobjekt]
getClass.call ([]); // [Objektarray]
getClass.call (neue Nummer (1)); // [Objektnummer]
// usw
Diese Funktion wird normalerweise zur Überprüfung von Objekten verwendet, aber in der Spezifikation kann die [[Klasse]] des Host-Objekts ein beliebiger Wert sein, einschließlich des Wertes des [[Klasse]] Attributs des integrierten Objekts, sodass sie theoretisch nicht 100% genau sein kann. Zum Beispiel ist die [[Klasse]] Eigenschaft des Dokuments.
Die Codekopie lautet wie folgt:
// in IE - "String", in anderen - "Funktion"
alert (getClass.call (document.childnodes.Item));
Konstruktor
Wie oben erwähnt, werden Objekte in ECMascript durch sogenannte Konstruktoren erstellt.
Der Konstruktor ist eine Funktion, die das neu erstellte Objekt erstellt und initialisiert.
Ein Konstruktor ist eine Funktion, die neu erstellte Objekte erstellt und initialisiert.
Die Objekterstellung (Speicherzuweisung) liegt in der Verantwortung der internen Methode des Konstruktors [[Konstrukt]]. Das Verhalten dieser internen Methode wird definiert, und alle Konstruktoren verwenden diese Methode, um neue Objekte zuzuweisen.
Die Initialisierung wird verwaltet, indem die Funktion das neue Objekt auf und ab aufgerufen wird, das durch die interne Methode des Konstruktors [[CALL]] verantwortlich ist.
Beachten Sie, dass auf den Benutzercode nur in der Initialisierungsphase zugegriffen werden kann, obwohl wir während der Initialisierungsphase verschiedene Objekte zurückgeben können (ignorieren die in der ersten Phase erstellten TIHS -Objekte):
Die Codekopie lautet wie folgt:
Funktion a () {
// neu erstellte Objekte aktualisieren
this.x = 10;
// Aber die Rückkehr ist ein anderes Objekt
Rückkehr [1, 2, 3];
}
var a = new a ();
console.log (ax, a); undefiniert, [1, 2, 3]
In Bezug auf Kapitel 15 Funktionen - Algorithmusabschnitt zum Erstellen von Funktionen können wir sehen, dass die Funktion ein nationales Objekt ist, einschließlich des [[Konstrukts]]] und [[Call]]] Eigenschaften sowie der Prototyp des angezeigten Prototyps - des Prototyps des zukünftigen Objekts: Native -Convention für native Einheimische, native Objekte, die in der PSUDOODOCODE LINWEISE) verwendet wurden.
Die Codekopie lautet wie folgt:
F = new NativeObject ();
F. [[Klasse]] = "Funktion"
.... // andere Attribute
F. [[CALL]] = <Referenz zur Funktion> // Funktion selbst
F. [[Konstrukt]] = Internalconstructor // gewöhnlicher interner Konstruktor
.... // andere Attribute
// vom F -Konstruktor erstellte Objektprototyp
__ObjectPrototype = {};
__ObjectPrototype.Constructor = f // {Dontenum}
F.Prototyp = __ObjectPrototype
[[CALL]]] ist die Hauptmethode, um Objekte mit Ausnahme des [[Klasse]] Attributs zu unterscheiden (das hier "Funktion" entspricht), sodass das interne Attribut des Objekts als Funktion bezeichnet wird. Wenn ein solches Objekt den Typeof -Operator verwendet, gibt es "Funktion" zurück. Es hängt jedoch hauptsächlich mit nativen Objekten zusammen. In einigen Fällen verwendet die Implementierung TypoF, um den Wert unterschiedlich zu erhalten, z. B. den Effekt von Fenster.Alert (...) im IE:
Die Codekopie lautet wie folgt:
// IE Browser - "Objekt", "Objekt", andere Browser - "Funktion", "Funktion"
alert (Object.Prototype.toString.call (window.alert));
alert (typeof fenster.alert); // "Objekt"
Die interne Methode [[Construct]] wird durch die Verwendung eines Konstruktors mit einem neuen Operator aktiviert, wie wir gesagt haben, für die Speicherzuweisung und die Erstellung von Objekten verantwortlich. Wenn es keine Parameter gibt, können die Klammern zum Aufrufen des Konstruktors auch weggelassen werden:
Die Codekopie lautet wie folgt:
Funktion a (x) {// Konstruktor а
this.x = x || 10;
}
// Wenn Sie keine Parameter übergeben, können auch Klammern weggelassen werden
var a = neu A; // oder neu a ();
Alarm (AX); // 10
// explizit übergeben Parameter x
var b = neu a (20);
Alarm (BX); // 20
Wir wissen auch, dass der Shis im Konstruktor (Initialisierungsphase) auf das neu erstellte Objekt eingestellt ist.
Lassen Sie uns den Algorithmus für die Erstellung von Objekten untersuchen.
Objekterkennungsalgorithmus
Das Verhalten der internen Methode [[Konstrukt]] kann wie folgt beschrieben werden:
Die Codekopie lautet wie folgt:
F. [[Konstrukt]] (InitialParameters):
O = new NativeObject ();
// Die Eigenschaft [[Klasse]] ist auf "Objekt" gesetzt
O. [[Klasse]] = "Objekt"
// Erhalten Sie das Objekt G, wenn Sie sich auf F.Prototyp beziehen
var __ObjectPrototype = f.Prototype;
// Wenn __ObjectPrototype ein Objekt ist, dann:
O. [[Prototyp]] = __ObjectPrototype
// Ansonsten:
O. [[Prototyp]] = Object.Prototyp;
// hier o. [[Prototyp]] ist der Prototyp des Objektobjekts
// f. [[Anruf]] wird angewendet, wenn das neu erstellte Objekt initialisiert wird
// Setzen Sie dies als neu erstelltes Objekt o
// Die Parameter entsprechen den InitialParametern in F.
R = f. [[Call]] (InitialParameters); this === o;
// Hier ist r der Rückgabewert von [[Anruf]]
// in js wie dieses:
// r = f.apply (o, initialParameters);
// Wenn R ein Objekt ist
return r
// Ansonsten
Rückkehr o
Bitte beachten Sie zwei Hauptfunktionen:
1. Erstens wird der Prototyp des neu erstellten Objekts aus der Prototyp -Eigenschaft der Funktion im Moment erhalten (dies bedeutet, dass die Prototypeigenschaften der beiden erstellten Objekte, die vomselben Konstruktor erstellt wurden, unterschiedlich sein können, da die Prototypeigenschaften der Funktion auch unterschiedlich sein können).
Zweitens, wie wir oben erwähnt haben, ist dies genau das Ergebnis, das für den gesamten neuen Bediener verwendet wird, wenn [[call]] das Objekt zurückgibt, wenn das Objekt initialisiert wird:
Die Codekopie lautet wie folgt:
Funktion a () {}
A.Prototype.x = 10;
var a = new a ();
Alarm (AX); // 10 Holen Sie es aus dem Prototyp
// Setzen Sie die Eigenschaft .Prototype auf ein neues Objekt
// Warum explizit deklariert. Das Attribut des Konstruktors wird unten erläutert
A.Prototype = {
Konstruktor: a,,
Y: 100
};
var b = neu a ();
// Das Objekt "B" hat neue Eigenschaften
Alarm (BX); // undefiniert
Alarm (von); // 100 erhalten Sie vom Prototyp
// aber der Prototyp von Objekt A kann immer noch das ursprüngliche Ergebnis erhalten
Alarm (AX); // 10 - Holen Sie sich vom Prototyp
Funktion b () {
this.x = 10;
return New Array ();
}
// Wenn der Konstruktor "B" nicht zurückgibt (oder dies zurückgibt)
// Dann kann dieses Objekt verwendet werden, aber die folgende Situation gibt ein Array zurück
var b = neu B ();
Alarm (BX); // undefiniert
alert (Object.Prototype.toString.call (b)); // [Objektarray]
Erfahren wir mehr über den Prototyp
Prototyp
Jedes Objekt hat einen Prototyp (mit Ausnahme einiger Systemobjekte). Die Prototyp -Kommunikation wird durch interne, implizite und indirekte Zugriff auf [[Prototyp]] Prototyp -Eigenschaften durchgeführt. Der Prototyp kann ein Objekt oder ein Nullwert sein.
Immobilienkonstruktor
Das obige Beispiel hat zwei wichtige Wissenspunkte. Der erste handelt von der Prototyp -Eigenschaft der Konstruktoreigenschaft der Funktion. Im Algorithmus der Funktionserstellung wissen wir, dass die Konstruktoreigenschaft als Prototyp -Eigenschaft der Funktion während der Funktionserstellungstufe festgelegt wird. Der Wert der Konstruktoreigenschaft ist ein wichtiger Hinweis auf die Funktion selbst:
Die Codekopie lautet wie folgt:
Funktion a () {}
var a = new a ();
Alarm (A.Constructor); // Funktion a () {} durch Delegation
alert (a.constructor === a); // WAHR
Normalerweise gibt es in diesem Fall ein Missverständnis: Es ist falsch, eine Eigenschaft als neu erstelltes Objekt selbst zu konstruieren, aber wie wir sehen können, gehört diese Eigenschaft zum Prototyp und greift durch Erbschaft auf das Objekt zu.
Durch Erben einer Instanz des Konstruktorattributs können Sie indirekt einen Verweis auf das Prototyp -Objekt erhalten:
Die Codekopie lautet wie folgt:
Funktion a () {}
A.Prototype.x = neue Zahl (10);
var a = new a ();
Alert (A.Constructor.Prototyp); // [Objektobjekt]
Alarm (AX); // 10 nach Prototypen
// der gleiche Effekt wie a. [[Prototyp]]. X
Alert (A.Constructor.Prototype.x); // 10
alert (a.constructor.prototype.x === ax); // WAHR
Bitte beachten Sie jedoch, dass die Konstruktor- und Prototypeigenschaften der Funktion nach Erstellen des Objekts neu definiert werden können. In diesem Fall verliert das Objekt den oben genannten Mechanismus. Wenn Sie den Prototyp -Prototyp eines Elements über das Prototyp -Attribut der Funktion bearbeiten (neue Objekte hinzufügen oder vorhandene Objekte ändern), sehen Sie das neu hinzugefügte Attribut in der Instanz.
Wenn wir jedoch die Prototyp -Eigenschaft der Funktion (durch Zuweisen eines neuen Objekts) vollständig ändern, geht der Verweis auf den ursprünglichen Konstruktor verloren, da das von uns erstellte Objekt nicht die Konstruktoreigenschaft enthält:
Die Codekopie lautet wie folgt:
Funktion a () {}
A.Prototype = {
x: 10
};
var a = new a ();
Alarm (AX); // 10
alert (a.constructor === a); // FALSCH!
Daher müssen Prototyp -Verweise auf Funktionen manuell wiederhergestellt werden:
Die Codekopie lautet wie folgt:
Funktion a () {}
A.Prototype = {
Konstruktor: a,,
x: 10
};
var a = new a ();
Alarm (AX); // 10
alert (a.constructor === a); // WAHR
Beachten Sie, dass das Konstruktor -Attribut zwar im Vergleich zum ursprünglichen fehlenden Prototyp manuell wiederhergestellt wird, das {Dontenum} -Feature verschwunden ist, dh die for -.. -in -Schleife in A.Prototype wird nicht mehr unterstützt, aber in der 5. Ausgabespezifikation bietet das [[[aufzählende]] Merkmal die Fähigkeit, die zahlreiche staatliche, staatliche, staatliche, staatliche staatliche Funktionen zu kontrollieren.
Die Codekopie lautet wie folgt:
var foo = {x: 10};
Object.DefineProperty (foo, "y", {
Wert: 20,
Aufzählbar: false // alka {Dontenum} = true
});
console.log (foo.x, foo.y); // 10, 20
für (var k in foo) {
console.log (k); // nur "x"
}
var xdesc = Object.getownPropertyDescriptor (foo, "x");
var ydesc = Object.getownPropertyDescriptor (foo, "y");
console.log (
xDesc.Enumerable, // true
ydesc.enumerable // false
);
Explizite Prototyp und implizite [[Prototyp]] Eigenschaften
Im Allgemeinen ist es falsch, durch die Prototyp -Eigenschaft der Funktion explizit auf den Prototyp eines Objekts zu verweisen. Es bezieht sich auf dasselbe Objekt, die Eigenschaft [[Prototyp]] des Objekts:
a. [[Prototyp] ---> Prototyp <---- A.Prototype
Darüber hinaus wird der [[Prototyp]] -Wert der Instanz tatsächlich an der Prototyp -Eigenschaft des Konstruktors erhalten.
Das Senden des Prototyp -Attributs wirkt sich jedoch nicht auf den erstellten Prototyp aus (es wird sich nur auswirken, wenn das Prototyp -Attribut des Konstruktors ändert), dh das neu erstellte Objekt verfügt über einen neuen Prototyp, und das erstellte Objekt bezieht sich weiterhin auf den ursprünglichen alten Prototyp (dieser Prototyp kann nicht mehr geändert werden).
Die Codekopie lautet wie folgt:
// Die Situation vor dem Ändern des A.Prototyp -Prototyps
a. [[Prototyp] ---> Prototyp <---- A.Prototype
// nach der Änderung
A.Prototyp ---> Neuer Prototyp // Neues Objekt hat diesen Prototyp
a. [[Prototyp] ---> Prototyp // auf dem ursprünglichen Prototyp des Starts
Zum Beispiel:
Die Codekopie lautet wie folgt:
Funktion a () {}
A.Prototype.x = 10;
var a = new a ();
Alarm (AX); // 10
A.Prototype = {
Konstruktor: a,,
x: 20
Y: 30
};
// Objekt A ist ein Wert, der aus dem Prototyp des Rohöls durch eine implizite [[[Prototyp]] erhalten wird
Alarm (AX); // 10
Alarm (ay) // undefiniert
var b = neu a ();
// Aber das neue Objekt ist der Wert des neuen Prototyps
Alarm (BX); // 20
Alarm (von) // 30
Daher ist es falsch für einige Artikel zu sagen, dass "dynamische Modifikation von Prototypen alle Objekte mit neuen Prototypen beeinflusst", und der neue Prototyp wirkt sich nur auf neu erstellte Objekte aus, nachdem der Prototyp geändert wurde.
Die Hauptregel hier ist: Der Prototyp des Objekts wird erstellt, wenn das Objekt erstellt wird, und kann danach nicht in ein neues Objekt modifiziert werden. Wenn weiterhin auf das gleiche Objekt verwiesen wird, kann es über den expliziten Prototyp des Konstruktors verwiesen werden. Nach dem Erstellen des Objekts können die Eigenschaften des Prototyps nur hinzugefügt oder geändert werden.
Nicht standardmäßige __Proto__-Attribute
Einige Implementierungen (z. B. Spidermonkey) liefern jedoch unstandehörige __Proto__ explizite Eigenschaften, um auf den Prototyp des Objekts zu verweisen:
Die Codekopie lautet wie folgt:
Funktion a () {}
A.Prototype.x = 10;
var a = new a ();
Alarm (AX); // 10
var __newPrototype = {
Konstruktor: a,,
x: 20,
Y: 30
};
// Verweis auf ein neues Objekt
A.Prototype = __NewPrototype;
var b = neu a ();
alert(bx); // 20
alert(by); // 30
// "a"对象使用的依然是旧的原型
alert(ax); // 10
alert(ay); // undefiniert
// 显式修改原型
a.__proto__ = __newPrototype;
// 现在"а"对象引用的是新对象
alert(ax); // 20
alert(ay); // 30
注意,ES5提供了Object.getPrototypeOf(O)方法,该方法直接返回对象的[[Prototype]]属性――实例的初始原型。 然而,和__proto__相比,它只是getter,它不允许set值。
Die Codekopie lautet wie folgt:
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // WAHR
对象独立于构造函数
因为实例的原型独立于构造函数和构造函数的prototype属性,构造函数完成了自己的主要工作(创建对象)以后可以删除。原型对象通过引用[[Prototype]]属性继续存在:
Die Codekopie lautet wie folgt:
function A() {}
A.prototype.x = 10;
var a = new A();
alert(ax); // 10
// 设置A为null - 显示引用构造函数
A = null;
// 但如果.constructor属性没有改变的话,
// 依然可以通过它创建对象
var b = new a.constructor();
alert(bx); // 10
// 隐式的引用也删除掉
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
// 通过A的构造函数再也不能创建对象了
// 但这2个对象依然有自己的原型
alert(ax); // 10
alert(bx); // 10
instanceof操作符的特性
我们是通过构造函数的prototype属性来显示引用原型的,这和instanceof操作符有关。该操作符是和原型链一起工作的,而不是构造函数,考虑到这一点,当检测对象的时候往往会有误解:
Die Codekopie lautet wie folgt:
if (foo instanceof Foo) {
...
}
这不是用来检测对象foo是否是用Foo构造函数创建的,所有instanceof运算符只需要一个对象属性――foo.[[Prototype]],在原型链中从Foo.prototype开始检查其是否存在。instanceof运算符是通过构造函数里的内部方法[[HasInstance]]来激活的。
让我们来看看这个例子:
Die Codekopie lautet wie folgt:
function A() {}
A.prototype.x = 10;
var a = new A();
alert(ax); // 10
alert(a instanceof A); // WAHR
// 如果设置原型为null
A.prototype = null;
// ..."a"依然可以通过a.[[Prototype]]访问原型
alert(ax); // 10
// 不过,instanceof操作符不能再正常使用了
// 因为它是从构造函数的prototype属性来实现的
alert(a instanceof A); // 错误,A.prototype不是对象
另一方面,可以由构造函数来创建对象,但如果对象的[[Prototype]]属性和构造函数的prototype属性的值设置的是一样的话,instanceof检查的时候会返回true:
Die Codekopie lautet wie folgt:
function B() {}
var b = new B();
alert(b instanceof B); // WAHR
function C() {}
var __proto = {
constructor: C
};
C.prototype = __proto;
b.__proto__ = __proto;
alert(b instanceof C); // WAHR
alert(b instanceof B); // FALSCH
原型可以存放方法并共享属性
大部分程序里使用原型是用来存储对象的方法、默认状态和共享对象的属性。
事实上,对象可以拥有自己的状态,但方法通常是一样的。 因此,为了内存优化,方法通常是在原型里定义的。 这意味着,这个构造函数创建的所有实例都可以共享找个方法。
Die Codekopie lautet wie folgt:
function A(x) {
this.x = x || 100;
}
A.prototype = (function () {
// 初始化上下文
// 使用额外的对象
var _someSharedVar = 500;
function _someHelper() {
alert('internal helper: ' + _someSharedVar);
}
function method1() {
alert('method1: ' + this.x);
}
function method2() {
alert('method2: ' + this.x);
_someHelper();
}
// 原型自身
zurückkehren {
constructor: A,
method1: method1,
method2: method2
};
}) ();
var a = new A(10);
var b = new A(20);
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
// 2个对象使用的是原型里相同的方法
alert(a.method1 === b.method1); // WAHR
alert(a.method2 === b.method2); // WAHR
读写属性
正如我们提到,读取和写入属性值是通过内部的[[Get]]和[[Put]]方法。这些内部方法是通过属性访问器激活的:点标记法或者索引标记法:
Die Codekopie lautet wie folgt:
// Schreiben
foo.bar = 10; // 调用了[[Put]]
console.log(foo.bar); // 10, 调用了[[Get]]
console.log(foo['bar']); // 效果一样
让我们用伪代码来看一下这些方法是如何工作的:
[[Get]]方法
[[Get]]也会从原型链中查询属性,所以通过对象也可以访问原型中的属性。
O.[[Get]](P):
Die Codekopie lautet wie folgt:
// 如果是自己的属性,就返回
if (O.hasOwnProperty(P)) {
return OP;
}
// 否则,继续分析原型
var __proto = O.[[Prototype]];
// 如果原型是null,返回undefined
// 这是可能的:最顶层Object.prototype.[[Prototype]]是null
if (__proto === null) {
return undefined;
}
// 否则,对原型链递归调用[[Get]],在各层的原型中查找属性
// 直到原型为null
return __proto.[[Get]](P)
请注意,因为[[Get]]在如下情况也会返回undefined:
Die Codekopie lautet wie folgt:
if (window.someObject) {
...
}
这里,在window里没有找到someObject属性,然后会在原型里找,原型的原型里找,以此类推,如果都找不到,按照定义就返回undefined。
注意:in操作符也可以负责查找属性(也会查找原型链):
Die Codekopie lautet wie folgt:
if ('someObject' in window) {
...
}
这有助于避免一些特殊问题:比如即便someObject存在,在someObject等于false的时候,第一轮检测就通不过。
[[Put]]方法
[[Put]]方法可以创建、更新对象自身的属性,并且掩盖原型里的同名属性。
O.[[Put]](P, V):
Die Codekopie lautet wie folgt:
// 如果不能给属性写值,就退出
if (!O.[[CanPut]](P)) {
zurückkehren;
}
// 如果对象没有自身的属性,就创建它
// 所有的attributes特性都是false
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributes: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Internal: false
});
}
// 如果属性存在就设置值,但不改变attributes特性
OP = V
zurückkehren;
Zum Beispiel:
Die Codekopie lautet wie folgt:
Object.prototype.x = 100;
var foo = {};
console.log(foo.x); // 100, 继承属性
foo.x = 10; // [[Setzen]]
console.log(foo.x); // 10, 自身属性
delete foo.x;
console.log(foo.x); // 重新是100,继承属性
请注意,不能掩盖原型里的只读属性,赋值结果将忽略,这是由内部方法[[CanPut]]控制的。
// 例如,属性length是只读的,我们来掩盖一下length试试
function SuperString() {
/* Nichts */
}
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, "abc"的长度
// 尝试掩盖
foo.length = 5;
console.log(foo.length); // 依然是3
但在ES5的严格模式下,如果掩盖只读属性的话,会保存TypeError错误。
属性访问器
内部方法[[Get]]和[[Put]]在ECMAScript里是通过点符号或者索引法来激活的,如果属性标示符是合法的名字的话,可以通过“.”来访问,而索引方运行动态定义名称。
Die Codekopie lautet wie folgt:
var a = {testProperty: 10};
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10, 动态属性通过索引的方式
这里有一个非常重要的特性――属性访问器总是使用ToObject规范来对待“.”左边的值。这种隐式转化和这句“在JavaScript中一切都是对象”有关系,(然而,当我们已经知道了,JavaScript里不是所有的值都是对象)。
如果对原始值进行属性访问器取值,访问之前会先对原始值进行对象包装(包括原始值),然后通过包装的对象进行访问属性,属性访问以后,包装对象就会被删除。
Zum Beispiel:
Die Codekopie lautet wie folgt:
var a = 10; // 原始值
// 但是可以访问方法(就像对象一样)
alert(a.toString()); // "10"
// 此外,我们可以在a上创建一个心属性
a.test = 100; // 好像是没问题的
// 但,[[Get]]方法没有返回该属性的值,返回的却是undefined
alert(a.test); // undefiniert
那么,为什么整个例子里的原始值可以访问toString方法,而不能访问新创建的test属性呢?
答案很简单:
首先,正如我们所说,使用属性访问器以后,它已经不是原始值了,而是一个包装过的中间对象(整个例子是使用new Number(a)),而toString方法这时候是通过原型链查找到的:
Die Codekopie lautet wie folgt:
// 执行a.toString()的原理:
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;
接下来,[[Put]]方法创建新属性时候,也是通过包装装的对象进行的:
Die Codekopie lautet wie folgt:
// 执行a.test = 100的原理:
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;
我们看到,在第3步的时候,包装的对象以及删除了,随着新创建的属性页被删除了――删除包装对象本身。
然后使用[[Get]]获取test值的时候,再一次创建了包装对象,但这时候包装的对象已经没有test属性了,所以返回的是undefined:
Die Codekopie lautet wie folgt:
// 执行a.test的原理:
1. wrapper = new Number(a);
2. wrapper.test; // undefiniert
这种方式解释了原始值的读取方式,另外,任何原始值如果经常用在访问属性的话,时间效率考虑,都是直接用一个对象替代它;与此相反,如果不经常访问,或者只是用于计算的话,到可以保留这种形式。
erben
我们知道,ECMAScript是使用基于原型的委托式继承。链和原型在原型链里已经提到过了。其实,所有委托的实现和原型链的查找分析都浓缩到[[Get]]方法了。
如果你完全理解[[Get]]方法,那JavaScript中的继承这个问题将不解自答了。
经常在论坛上谈论JavaScript中的继承时,我都是用一行代码来展示,事实上,我们不需要创建任何对象或函数,因为该语言已经是基于继承的了,代码如下:
Die Codekopie lautet wie folgt:
alert(1..toString()); // "1"
我们已经知道了[[Get]]方法和属性访问器的原理了,我们来看看都发生了什么:
1.首先,从原始值1,通过new Number(1)创建包装对象
2.然后toString方法是从这个包装对象上继承得到的
为什么是继承的? 因为在ECMAScript中的对象可以有自己的属性,包装对象在这种情况下没有toString方法。 因此它是从原理里继承的,即Number.prototype。
注意有个微妙的地方,在上面的例子中的两个点不是一个错误。第一点是代表小数部分,第二个才是一个属性访问器:
Die Codekopie lautet wie folgt:
1.toString(); // 语法错误!
(1).toString(); // OK
1..toString(); // OK
1['toString'](); // OK
原型链
让我们展示如何为用户定义对象创建原型链,非常简单:
Die Codekopie lautet wie folgt:
function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new A();
alert([ax, ay]); // 10 (自身), 20 (继承)
function B() {}
// 最近的原型链方式就是设置对象的原型为另外一个新对象
B.prototype = new A();
// 修复原型的constructor属性,否则的话是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10, 20, 2个都是继承的
// [[Get]] bx:
// bx (no) -->
// b.[[Prototype]].x (yes) - 10
// [[Get]] by
// by (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype
这种方法有两个特性:
首先,B.prototype将包含x属性。乍一看这可能不对,你可能会想x属性是在A里定义的并且B构造函数也是这样期望的。尽管原型继承正常情况是没问题的,但B构造函数有时候可能不需要x属性,与基于class的继承相比,所有的属性都复制到后代子类里了。
尽管如此,如果有需要(模拟基于类的继承)将x属性赋给B构造函数创建的对象上,有一些方法,我们后来来展示其中一种方式。
其次,这不是一个特征而是缺点――子类原型创建的时候,构造函数的代码也执行了,我们可以看到消息"A.[[Call]] activated"显示了两次――当用A构造函数创建对象赋给B.prototype属性的时候,另外一场是a对象创建自身的时候!
下面的例子比较关键,在父类的构造函数抛出的异常:可能实际对象创建的时候需要检查吧,但很明显,同样的case,也就是就是使用这些父对象作为原型的时候就会出错。
Die Codekopie lautet wie folgt:
function A(param) {
if (!param) {
throw 'Param required';
}
this.param = param;
}
A.prototype.x = 10;
var a = new A(20);
alert([ax, a.param]); // 10, 20
function B() {}
B.prototype = new A(); // Fehler
此外,在父类的构造函数有太多代码的话也是一种缺点。
解决这些“功能”和问题,程序员使用原型链的标准模式(下面展示),主要目的就是在中间包装构造函数的创建,这些包装构造函数的链里包含需要的原型。
Die Codekopie lautet wie folgt:
function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new A();
alert([ax, ay]); // 10 (自身), 20 (集成)
function B() {
// 或者使用A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}
// 继承:通过空的中间构造函数将原型连在一起
var F = function () {};
F.prototype = A.prototype; // Zitat
B.prototype = new F();
B.superproto = A.prototype; // 显示引用到另外一个原型上, "sugar"
// 修复原型的constructor属性,否则的就是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10 (自身), 20 (集成)
注意,我们在b实例上创建了自己的x属性,通过B.superproto.constructor调用父构造函数来引用新创建对象的上下文。
我们也修复了父构造函数在创建子原型的时候不需要的调用,此时,消息"A.[[Call]] activated"在需要的时候才会显示。
为了在原型链里重复相同的行为(中间构造函数创建,设置superproto,恢复原始构造函数),下面的模板可以封装成一个非常方面的工具函数,其目的是连接原型的时候不是根据构造函数的实际名称。
Die Codekopie lautet wie folgt:
function inherit(child, parent) {
var F = function () {};
F.prototype = parent.prototype
child.prototype = new F();
child.prototype.constructor = child;
child.superproto = parent.prototype;
Kind zurückkehren;
}
因此,继承:
Die Codekopie lautet wie folgt:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A); // 连接原型
var b = new B();
alert(bx); // 10, 在A.prototype查找到
也有很多语法形式(包装而成),但所有的语法行都是为了减少上述代码里的行为。
例如,如果我们把中间的构造函数放到外面,就可以优化前面的代码(因此,只有一个函数被创建),然后重用它:
Die Codekopie lautet wie folgt:
var inherit = (function(){
function F() {}
return function (child, parent) {
F.prototype = parent.prototype;
child.prototype = new F;
child.prototype.constructor = child;
child.superproto = parent.prototype;
Kind zurückkehren;
};
}) ();
由于对象的真实原型是[[Prototype]]属性,这意味着F.prototype可以很容易修改和重用,因为通过new F创建的child.prototype可以从child.prototype的当前值里获取[[Prototype]]:
Die Codekopie lautet wie folgt:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A);
B.prototype.y = 20;
B.prototype.foo = function () {
alert("B#foo");
};
var b = new B();
alert(bx); // 10, 在A.prototype里查到
function C() {}
inherit(C, B);
// 使用"superproto"语法糖
// 调用父原型的同名方法
C.ptototype.foo = function () {
C.superproto.foo.call(this);
alert("C#foo");
};
var c = new C();
alert([cx, cy]); // 10, 20
c.foo(); // B#foo, C#foo
注意,ES5为原型链标准化了这个工具函数,那就是Object.create方法。ES3可以使用以下方式实现:
Die Codekopie lautet wie folgt:
Object.create ||
Object.create = function (parent, properties) {
function F() {}
F.prototype = parent;
var child = new F;
for (var k in properties) {
child[k] = properties[k].value;
}
Kind zurückkehren;
}
// Verwendung
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20
此外,所有模仿现在基于类的经典继承方式都是根据这个原则实现的,现在可以看到,它实际上不是基于类的继承,而是连接原型的一个很方便的代码重用。
abschließend
本章内容已经很充分和详细了,希望这些资料对你有用,并且消除你对ECMAScript的疑问,如果你有任何问题,请留言,我们一起讨论。