Heim  >  Artikel  >  Web-Frontend  >  Vertiefendes Verständnis der JavaScript-Reihe (18): ECMAScript-Implementierung der objektorientierten Programmierung_Grundkenntnisse

Vertiefendes Verständnis der JavaScript-Reihe (18): ECMAScript-Implementierung der objektorientierten Programmierung_Grundkenntnisse

WBOY
WBOYOriginal
2016-05-16 16:11:121353Durchsuche

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:

Code kopieren Der Code lautet wie folgt:

ECMAScript ist eine objektorientierte Programmiersprache, die die Delegierung der Vererbung basierend auf Prototypen unterstützt.

ECMAScript ist eine objektorientierte Sprache, die prototypbasierte delegierte Vererbung unterstützt.
Wir werden es anhand der 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, was einige Artikel sagen: „In JavaScript ist alles ein Objekt“, falsch (nicht ganz). rechts) sind primitive Werte einige der Datentypen, die wir hier besprechen werden.

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:

Code kopieren Der Code lautet wie folgt:

var a = undefiniert;
var b = null;
var c = true;
var d = 'test';
var e = 10;

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.

Code kopieren Der Code lautet wie folgt:

warning(typeof null); // "object"

Der Grund für die Anzeige von „object“ liegt darin, dass die Spezifikation vorschreibt, dass der Typ des Zeichenfolgenwerts „object“ für einen Nullwert zurückgibt.

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:

Code kopieren Der Code lautet wie folgt:

var x = { // Objekt „x“ hat 3 Attribute: a, b, c
a: 10, // Originalwert
b: {z: 100}, // Objekt „b“ hat ein Attribut z
c: function () { // Funktion (Methode)
alarm('method x.c');
}
};

alarm(x.a); // 10
Alert(x.b); // [Objekt Objekt]
alarm(x.b.z); // 100
x.c(); // 'Methode x.c'

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:

Code kopieren Der Code lautet wie folgt:

var foo = {x: 10};

//Neues Attribut hinzufügen
foo.y = 20;
console.log(foo); // {x: 10, y: 20}

// Attributwert in Funktion
ändern foo.x = Funktion () {
console.log('foo.x');
};

foo.x(); // 'foo.x'

// Attribut
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 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.

Code kopieren Der Code lautet wie folgt:

var foo = {x: 10};

// Objekt einfrieren
Object.freeze(foo);
console.log(Object.isFrozen(foo)); // true

// Kann nicht geändert werden
foo.x = 100;

// Kann nicht erweitert werden
foo.y = 200;

//
kann nicht gelöscht werden foo.x löschen;

console.log(foo); // {x: 10}

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:

Code kopieren Der Code lautet wie folgt:

var foo = {x : 10};

Object.defineProperty(foo, "y", {
Wert: 20,
beschreibbar: false, // schreibgeschützt
konfigurierbar: false // Nicht konfigurierbar
});

// Kann nicht geändert werden
foo.y = 200;

//
kann nicht gelöscht werden foo.y löschen; // false

// Präventions- und Kontrollerweiterung
Object.preventExtensions(foo);
console.log(Object.isExtensible(foo)); // false

//Es können keine neuen Attribute hinzugefügt werden
foo.z = 30;

console.log(foo); {x: 10, y: 20}

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.

Code kopieren Der Code lautet wie folgt:

var c = new Boolean(true);
var d = new String('test');
var e = neue Zahl(10);

//In ursprünglichen Wert konvertieren
// Funktion ohne neues Schlüsselwort
verwenden с = Boolean(c);
d = String(d);
e = Zahl(e);

// Zurück zum Objekt
konvertieren с = Objekt(c);
d = Objekt(d);
e = Objekt(e);

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:

Code kopieren Der Code lautet wie folgt:

// Entspricht new Array(1, 2, 3);
// Oder array = new Array();
// array[0] = 1;
// array[1] = 2;
// array[2] = 3;
var array = [1, 2, 3];

// Entspricht
// var object = new Object();
// object.a = 1;
// object.b = 2;
// object.c = 3;
var object = {a: 1, b: 2, c: 3};

// Entspricht new RegExp("^\d $", "g")
var re = /^d $/g;

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.

Code kopieren Der Code lautet wie folgt:

var getClass = Object.prototype.toString;

Objekt = Zahl;

var foo = neues Objekt;
alarm([foo, getClass.call(foo)]); // 0, "[object Number]"

var bar = {};

// Rhino, SpiderMonkey 1.7 – 0, „[Objektnummer]“
// Andere: immer noch „[object Object]“, „[object Object]“
alarm([bar, getClass.call(bar)]);

//Array hat den gleichen Effekt
Array = Zahl;

foo = neues Array;
alarm([foo, getClass.call(foo)]); // 0, "[object Number]"

bar = [];

// Rhino, SpiderMonkey 1.7 – 0, „[Objektnummer]“
// Andere: still "", "[object Object]"
alarm([bar, getClass.call(bar)]);

// Aber für RegExp wird die Semantik des Literals nicht geändert. Semantik des Literalen
// wird nicht in allen getesteten Implementierungen geändert

RegExp = Number;

foo = new RegExp;
alarm([foo, getClass.call(foo)]); // 0, "[object Number]"

bar = /(?!)/g;
Alert([bar, getClass.call(bar)]); // /(?!)/g, "[object RegExp]"

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:

Code kopieren Der Code 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")); // true, false, true, false
}

// 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 5. Ausgabe der ES-Spezifikation behoben. Unabhängig davon, ob sie auf Literalen oder Konstruktoren basiert, erstellen die regulären Regeln neue Objekte.

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:

Code kopieren Der Code lautet wie folgt:

var a = {x: 10};
a['y'] = 20;
a.z = 30;

var b = neue Zahl(1);
b.x = 10;
b.y = 20;
b['z'] = 30;

var c = neue Funktion('');
c.x = 10;
c.y = 20;
c['z'] = 30;

// Warten Sie, ein Untertyp eines beliebigen Objekts „subtype“

Da Objekte in ECMAScript auch leer sein können, ist das Konzept von „Hash“ auch hier falsch:

Code kopieren Der Code lautet wie folgt:

Object.prototype.x = 10;

var a = {}; // Leeren „Hash“ erstellen

Alert(a["x"]); // 10, aber nicht leer
Alert(a.toString); // Funktion

a["y"] = 20; // Neues Schlüssel-Wert-Paar zu "hash" hinzufügen
alarm(a["y"]); // 20

Object.prototype.y = 20; // Prototypattribute hinzufügen

delete a["y"]; // Löschen
warning(a["y"]); // Aber der Schlüssel und der Wert haben hier immer noch Werte - 20

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:

Code kopieren Der Code lautet wie folgt:

var aHashTable = Object.create(null);
console.log(aHashTable.toString); // Undefiniert

Darüber hinaus verfügen einige Eigenschaften über spezielle Getter/Setter-Methoden, sodass es ebenfalls zu Verwirrung über dieses Konzept kommen kann:
Code kopieren Der Code lautet wie folgt:

var a = new String("foo");
a['length'] = 10;
alarm(a['length']); // 3

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:

Code kopieren Der Code lautet wie folgt:

a = {}
a.class # Hash

a.length # 0

# neues „Schlüssel-Wert“-Paar
a['length'] = 10;

# Semantisch gesehen werden Punkte verwendet, um auf Eigenschaften oder Methoden zuzugreifen, nicht auf Schlüssel

a.Länge # 1

#Der Indexer greift auf den Schlüssel im Hash zu

a['length'] # 10

# Es ähnelt der dynamischen Deklaration einer Hash-Klasse für ein vorhandenes Objekt
# Dann deklarieren Sie neue Eigenschaften oder Methoden

Klasse Hash
def z
100
Ende
Ende

# Auf neue Attribute kann zugegriffen werden

a.z # 100

# Aber nicht „Schlüssel“

a['z'] # nil

Der ECMA-262-3-Standard definiert das Konzept von „Hashes“ (und ähnlichem) nicht. Wenn es jedoch eine solche Strukturtheorie gibt, ist es möglich, das Objekt nach dieser zu benennen.

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:

Code kopieren Der Code lautet wie folgt:

var a = neue Zahl(1);
var primitiveA = Number(a); // Impliziter „valueOf“-Aufruf
var alsoPrimitiveA = a.valueOf(); // Expliziter Aufruf

Warnung([
Typ eines, // „Objekt“
typeof primitiveA, // „number“
typeof alsoPrimitiveA // "number"
]);

Dieser Ansatz ermöglicht es Objekten, an verschiedenen Vorgängen teilzunehmen, wie zum Beispiel:
Code kopieren Der Code lautet wie folgt:

var a = neue Zahl(1);
var b = neue Zahl(2);

alarm(a b); // 3

// Sogar

var c = {
x: 10,
Jahr: 20,
valueOf: function () {
Geben Sie this.x this.y;
zurück }
};

var d = {
x: 30,
Jahr: 40,
//Entspricht der valueOf-Funktion von c
valueOf: c.valueOf
};

alarm(c d); // 100

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:

Code kopieren Der Code lautet wie folgt:

var a = {};
Alert(a.valueOf() === a); // true, „valueOf“ gibt dies zurück

var d = neues Datum();
alarm(d.valueOf()); // time
alarm(d.valueOf() === d.getTime()); // true

Darüber hinaus verfügen Objekte über eine primitivere Darstellung – eine String-Darstellung. Diese toString-Methode ist zuverlässig und wird automatisch für bestimmte Vorgänge verwendet:
Code kopieren Der Code lautet wie folgt:

var a = {
valueOf: function () {
Rückgabe 100;
},
toString: Funktion () {
Geben Sie '__test' zurück;
}
};

// Bei dieser Operation wird die toString-Methode automatisch
aufgerufen alarm(a); // "__test"

// Aber hier heißt die Methode valueOf()
alarm(a 10); // 110

// Aber sobald valueOf gelöscht wird
// toString kann erneut automatisch aufgerufen werden
a.valueOf;
löschen alarm(a 10); // "_test10"

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):

Code kopieren Der Code lautet wie folgt:

var n = Object(1); // [Objektnummer]
var s = Object('test'); // [object String]

//Etwas Ähnliches, Sie können auch den neuen Operator
verwenden var b = new Object(true); // [object Boolean]

// Wenn der Parameter new Object verwendet wird, wird ein einfaches Objekt erstellt
var o = new Object(); // [object Object]

// Wenn der Parameter ein vorhandenes Objekt ist
// Das Ergebnis der Erstellung besteht darin, einfach das Objekt
zurückzugeben var a = [];
alarm(a === new Object(a)); // true
alarm(a === Object(a)); // true

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:

Code kopieren Der Code lautet wie folgt:

var a = Array(1, 2, 3); // [Objekt-Array]
var b = new Array(1, 2, 3); // [object Array]
var c = [1, 2, 3]; // [Objektarray]

var d = Function(''); // [object Function]
var e = new Function(''); // [object Function]

Bei der Verwendung einiger Operatoren gibt es auch einige explizite und implizite Konvertierungen:
Code kopieren Der Code lautet wie folgt:

var a = 1;
var b = 2;

// Implizit
var c = a b; // 3, Zahl
var d = a b '5' // "35", string

// explizit
var e = '10'; // "10", string
var f = e; // 10, Zahl
var g = parseInt(e, 10); // 10, Zahl

// Warte

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.

Code kopieren Der Code lautet wie folgt:

var foo = {};

Object.defineProperty(foo, "x", {
Wert: 10,
beschreibbar: true, // das ist {ReadOnly} = false
enumerable: false, // das ist {DontEnum} = true
konfigurierbar: true // d. h. {DontDelete} = false
});

console.log(foo.x); // 10

// Holen Sie sich die Feature-Set-Attribute
über den Deskriptor var desc = Object.getOwnPropertyDescriptor(foo, "x");

console.log(desc.enumerable); // false
console.log(desc.writable); // true
// Warte

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:

Code kopieren Der Code lautet wie folgt:

var getClass = Object.prototype.toString;

getClass.call({}); // [object Object]
getClass.call([]); // [Objektarray]
getClass.call(new Number(1)); // [Objektnummer]
// Warte

Diese Funktion wird normalerweise zum Überprüfen von Objekten verwendet, aber die Spezifikation besagt, dass die [[Klasse]] des Hostobjekts ein beliebiger Wert sein kann, einschließlich des Werts des [[Klasse]]-Attributs des integrierten Objekts, also theoretisch Die Richtigkeit kann nicht zu 100 % garantiert werden. Beispielsweise gibt das Attribut [[Class]] der Methode document.childNodes.item(...) im IE „String“ zurück, in anderen Implementierungen jedoch „Function“.
Code kopieren Der Code lautet wie folgt:

// im IE – „String“, im anderen – „Funktion“
Alert(getClass.call(document.childNodes.item));

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):

Code kopieren Der Code lautet wie folgt:

Funktion A() {
// Aktualisiere das neu erstellte Objekt
this.x = 10;
// Aber es gibt ein anderes Objekt zurück
return [1, 2, 3];
}

var a = new A();
console.log(a.x, a); undefiniert, [1, 2, 3]

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).

Code kopieren Der Code lautet wie folgt:

F = new NativeObject();

F.[[Klasse]] = „Funktion“

.... // Andere Attribute

F.[[Call]] = // Funktion selbst

F.[[Construct]] = internalConstructor // Gewöhnlicher interner Konstruktor

.... // Andere Attribute

// Vom F-Konstruktor erstellter Objektprototyp
__objectPrototype = {};
__objectPrototype.constructor = F // {DontEnum}
F.prototype = __objectPrototype

[[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:

Code kopieren Der Code lautet wie folgt:

// Im IE-Browser – „Object“, „object“, andere Browser – „Function“, „function“
Alert(Object.prototype.toString.call(window.alert));
warning(typeof window.alert); // "Object"

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:

Code kopieren Der Code lautet wie folgt:

Funktion A(x) { // Konstruktor А
this.x = x ||. 10;
}

// Wenn keine Parameter übergeben werden, können die Klammern weggelassen werden
var a = neues A; // oder neues A();
alarm(a.x); // 10

//Parameter x explizit übergeben
var b = neues A(20);
alarm(b.x); // 20

Wir wissen auch, dass shis im Konstruktor (Initialisierungsphase) auf das neu erstellte Objekt eingestellt ist.

Lassen Sie uns den Objekterstellungsalgorithmus untersuchen.

Algorithmus zur Objekterstellung

Das Verhalten der internen Methode [[Construct]] kann wie folgt beschrieben werden:

Code kopieren Der Code lautet wie folgt:

F.[[Konstrukt]](initialParameters):

O = new NativeObject();

// Eigenschaft [[Klasse]] ist auf „Objekt“
gesetzt O.[[Klasse]] = „Objekt“

// Holen Sie sich das Objekt g
, wenn Sie auf F.prototype verweisen var __objectPrototype = F.prototype;

// Wenn __objectPrototype ein Objekt ist, dann:
O.[[Prototype]] = __objectPrototype
// Ansonsten:
O.[[Prototype]] = Object.prototype;
// Hier ist O.[[Prototype]] der Prototyp des Object-Objekts

// F.[[Call]]
wird beim Initialisieren des neu erstellten Objekts angewendet. // Setze dies auf das neu erstellte Objekt O
//Die Parameter sind die gleichen wie die Anfangsparameter in F
R = F.[[Call]](initialParameters); this === O;
// Hier ist R der Rückgabewert von [[Call]]
// Zeigen Sie es in JS an, etwa so:
// R = F.apply(O, initialParameters);

// Wenn R ein Objekt ist
Rückkehr R
// Ansonsten
Rückkehr O

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:

Code kopieren Der Code lautet wie folgt:

Funktion A() {}
A.prototype.x = 10;

var a = new A();
Alert(a.x); // 10 –
vom Prototyp abrufen
// Setze die .prototype-Eigenschaft auf das neue Objekt
// Warum die .constructor-Eigenschaft explizit deklariert wird, wird unten erklärt
A.prototype = {
Konstruktor: A,
Jahr: 100
};

var b = new A();
// Objekt „b“ hat neue Eigenschaften
alarm(b.x); // undefiniert
Alert(b.y); // 100 –
vom Prototyp abrufen
// Aber der Prototyp eines Objekts kann immer noch das ursprüngliche Ergebnis erhalten
Alert(a.x); // 10 –
vom Prototyp abrufen
Funktion B() {
this.x = 10;
return new Array();
}

// Wenn der „B“-Konstruktor nicht zurückgibt (oder dies zurückgibt)
// Dann kann dieses Objekt verwendet werden, aber im folgenden Fall wird array
zurückgegeben var b = new B();
alarm(b.x); // undefiniert
alarm(Object.prototype.toString.call(b)); // [object Array]

Schauen wir uns den Prototyp genauer an

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:

Code kopieren Der Code lautet wie folgt:

Funktion A() {}
var a = new A();
Alert(a.constructor); // Funktion A() {}, durch Delegation
Alert(a.constructor === A); // true

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:

Code kopieren Der Code lautet wie folgt:

Funktion A() {}
A.prototype.x = neue Nummer(10);

var a = new A();
Alert(a.constructor.prototype); // [object Object]

Alert(a.x); // 10, über Prototyp
// Gleicher Effekt wie a.[[Prototype]].x
Alert(a.constructor.prototype.x); // 10

Alert(a.constructor.prototype.x === a.x); // true

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:

Code kopieren Der Code lautet wie folgt:

Funktion A() {}
A.prototype = {
x: 10
};

var a = new A();
alarm(a.x); // 10
Alert(a.constructor === A); // false!

Daher muss der Prototyp-Verweis auf die Funktion manuell wiederhergestellt werden:
Code kopieren Der Code lautet wie folgt:

Funktion A() {}
A.prototype = {
Konstruktor: A,
x: 10
};

var a = new A();
alarm(a.x); // 10
Alert(a.constructor === A); // true

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.

Code kopieren Der Code lautet wie folgt:

var foo = {x: 10};

Object.defineProperty(foo, "y", {
Wert: 20,
aufzählbar: false // auch bekannt als {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
);

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).

Code kopieren Der Code lautet wie folgt:

// Die Situation vor der Änderung des A.prototype-Prototyps
a.[[Prototyp]] ----> Prototyp <---- A.prototyp

// Nach der Änderung
A.prototype ----> Neuer Prototyp // Neue Objekte werden diesen Prototyp haben
a.[[Prototyp]] ----> Prototyp // Booten Sie den ursprünglichen Prototyp

Zum Beispiel:

Code kopieren Der Code lautet wie folgt:

Funktion A() {}
A.prototype.x = 10;

var a = new A();
alarm(a.x); // 10

A.prototype = {
Konstruktor: A,
x: 20
Jahr: 30
};

// Objekt a ist ein Wert, der aus dem Prototyp des Rohöls durch die implizite [[Prototyp]]-Referenz
erhalten wird alarm(a.x); // 10
alarm(a.y) // undefiniert

var b = new A();

// Aber das neue Objekt ist der vom neuen Prototyp erhaltene Wert
alarm(b.x); // 20
alarm(b.y) // 30

Daher heißt es in einigen Artikeln, dass „sich eine dynamische Änderung des Prototyps auf alle Objekte auswirkt und alle Objekte neue Prototypen haben“, was falsch ist. Der neue Prototyp wird erst auf neu erstellte Objekte wirksam, nachdem der Prototyp geändert wurde.

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:

Code kopieren Der Code lautet wie folgt:

Funktion A() {}
A.prototype.x = 10;

var a = new A();
alarm(a.x); // 10

var __newPrototype = {
Konstruktor: A,
x: 20,
Jahr: 30
};

//Referenz auf neues Objekt
A.prototype = __newPrototype;

var b = new A();
alarm(b.x); // 20
alarm(b.y); // 30

// Das „a“-Objekt verwendet immer noch den alten Prototyp
alarm(a.x); // 10
alarm(a.y); // undefiniert

// Den Prototyp explizit ändern
a.__proto__ = __newPrototype;

// Jetzt verweist das „а“-Objekt auf das neue Objekt
alarm(a.x); // 20
alarm(a.y); // 30

Beachten Sie, dass ES5 die Methode Object.getPrototypeOf(O) bereitstellt, die direkt die Eigenschaft [[Prototype]] des Objekts zurückgibt – den ursprünglichen Prototyp der Instanz. Im Vergleich zu __proto__ ist es jedoch nur ein Getter und lässt keine festgelegten Werte zu.
Code kopieren Der Code lautet wie folgt:

var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // true

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:

Code kopieren Der Code lautet wie folgt:

Funktion A() {}
A.prototype.x = 10;

var a = new A();
alarm(a.x); // 10

// A auf Null setzen – Referenzkonstruktor anzeigen
A = null;

// Aber wenn sich die .constructor-Eigenschaft nicht geändert hat,
// Sie können damit immer noch Objekte erstellen
var b = new a.constructor();
alarm(b.x); // 10

// Implizite Referenzen werden ebenfalls gelöscht
löschen Sie a.constructor.prototype.constructor;
löschen Sie b.constructor.prototype.constructor;

// Objekte können nicht mehr über den Konstruktor von A
erstellt werden // Aber diese beiden Objekte haben noch ihre eigenen Prototypen
alarm(a.x); // 10
alarm(b.x); // 10

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:

Code kopieren Der Code lautet wie folgt:

if (foo-Instanz von Foo) {
...
}

Dies wird nicht verwendet, um zu erkennen, ob das Objekt foo mit dem Foo-Konstruktor erstellt wurde. Alle Instanzen von Operatoren erfordern nur eine Objekteigenschaft – foo.[[Prototype]], und überprüfen deren Existenz beginnend mit Foo.prototype in der Prototypenkette. Der Instanzoperator wird über die interne Methode [[HasInstance]] im Konstruktor aktiviert.

Sehen wir uns dieses Beispiel an:

Code kopieren Der Code lautet wie folgt:

Funktion A() {}
A.prototype.x = 10;

var a = new A();
alarm(a.x); // 10

Alert(eine Instanz von A); // true

// Wenn der Prototyp auf null gesetzt ist
A.prototype = null;

// ..."a" kann weiterhin über a.[[Prototype]]
auf den Prototyp zugreifen alarm(a.x); // 10

// Der Instanzoperator kann jedoch nicht mehr normal verwendet werden
// Weil es über das Prototypattribut des Konstruktors
implementiert wird Alert(eine Instanz von A); // Fehler, A.prototype ist kein Objekt

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:

Code kopieren Der Code lautet wie folgt:

Funktion B() {}
var b = new B();

Alert(b Instanz von B); // true

Funktion C() {}

var __proto = {
Konstruktor: C
};

C.prototype = __proto;
b.__proto__ = __proto;

Alert(b Instanz von C); // true
Alert(b Instanz von B); // false

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.

Code kopieren Der Code lautet wie folgt:

Funktion A(x) {
this.x = x ||. 100;
}

A.prototype = (function () {

// Kontext initialisieren
// Zusätzliche Objekte verwenden

var _someSharedVar = 500;

Funktion _someHelper() {
Alert('interner Helfer: ' _someSharedVar);
}

Funktion method1() {
alarm('method1: ' this.x);
}

Funktion method2() {
alarm('method2: ' this.x);
_someHelper();
}

// Prototyp selbst
Geben Sie {
zurück ​ Konstruktor: A,
Methode1: Methode1,
Methode2: Methode2
};

})();

var a = neues A(10);
var b = neues A(20);

a.method1(); // method1: 10
a.method2(); // Methode2: 10, interner Helfer: 500

b.method1(); // method1: 20
b.method2(); // Methode2: 20, interner Helfer: 500

// Die beiden Objekte verwenden im Prototyp
dieselbe Methode Alert(a.method1 === b.method1); // true
Alert(a.method2 === b.method2); // true

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:

Code kopieren Der Code lautet wie folgt:

// schreibe
foo.bar = 10; // Wird [[Put]]
genannt
console.log(foo.bar); // 10, aufgerufen [[Get]]
console.log(foo['bar']); // Gleicher Effekt

Sehen wir uns an, wie diese Methoden im Pseudocode funktionieren:

[[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):

Code kopieren Der Code lautet wie folgt:

// Wenn es Ihr eigenes Attribut ist, geben Sie
zurück if (O.hasOwnProperty(P)) {
Rückkehr O.P;
}

// Andernfalls analysieren Sie den Prototyp weiter
var __proto = O.[[Prototyp]];

// Wenn der Prototyp null ist, undefiniert
zurückgeben // Das ist möglich: Der Object.prototype.[[Prototype]] der obersten Ebene ist null
if (__proto === null) {
Rückgabe undefiniert;
}

// Andernfalls rufen Sie [[Get]] rekursiv in der Prototypenkette auf und suchen Sie nach Attributen in den Prototypen jeder Ebene
// Bis der Prototyp null ist
return __proto.[[Get]](P)

Bitte beachten Sie, dass [[Get]] in den folgenden Situationen auch undefiniert zurückgibt:
Code kopieren Der Code lautet wie folgt:

if (window.someObject) {
...
}

Wenn die someObject-Eigenschaft nicht im Fenster gefunden wird, wird sie im Prototyp gesucht, dann im Prototyp des Prototyps usw. Wenn sie nicht gefunden wird, wird gemäß der Definition undefiniert zurückgegeben.

Hinweis: Der in-Operator kann auch für die Suche nach Eigenschaften verantwortlich sein (er sucht auch nach der Prototypenkette):

Code kopieren Der Code lautet wie folgt:

if ('someObject' in window) {
...
}

Dies hilft, einige spezielle Probleme zu vermeiden: Selbst wenn someObject vorhanden ist und someObject gleich false ist, schlägt die erste Erkennungsrunde fehl.

[[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):

Code kopieren Der Code lautet wie folgt:

// Wenn der Wert nicht in das Attribut geschrieben werden kann, beenden Sie
if (!O.[[CanPut]](P)) {
Zurück;
}

// Wenn das Objekt keine eigenen Eigenschaften hat, erstellen Sie es
// Alle Attribute sind falsch
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, Attribute: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Intern: falsch
});
}

// Legen Sie den Wert fest, wenn das Attribut vorhanden ist, aber ändern Sie nicht die Attributeigenschaft
O.P = V

zurück;

Zum Beispiel:
Code kopieren Der Code lautet wie folgt:

Object.prototype.x = 100;

var foo = {};
console.log(foo.x); // 100, geerbte Eigenschaften

foo.x = 10; // [[Put]]
console.log(foo.x); // 10, eigene Attribute

foo.x löschen;
console.log(foo.x); //auf 100 zurücksetzen, Eigenschaften erben
Bitte beachten Sie, dass schreibgeschützte Eigenschaften im Prototyp nicht maskiert werden können und die Zuweisungsergebnisse ignoriert werden. Dies wird durch die interne Methode [[CanPut]] gesteuert.

// 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


Wenn jedoch im strikten Modus von ES5 das schreibgeschützte Attribut maskiert ist, wird ein TypeError gespeichert.

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.

Code kopieren Der Code lautet wie folgt:

var a = {testProperty: 10};

Alert(a.testProperty); // 10, klicken Sie auf
alarm(a['testProperty']); // 10, index

var propertyName = 'Property';
Alert(a['test' propertyName]); // 10, dynamische Eigenschaften werden indiziert

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:

Code kopieren Der Code lautet wie folgt:

var a = 10; // Originalwert

// Aber auf Methoden kann zugegriffen werden (genau wie auf Objekte)
alarm(a.toString()); // "10"

// Zusätzlich können wir ein Herzattribut für ein
erstellen a.test = 100; // Es scheint kein Problem zu sein

// Die Methode [[Get]] gibt jedoch nicht den Wert der Eigenschaft zurück, sondern undefiniert
Alert(a.test); // undefiniert

Warum kann also der Originalwert im gesamten Beispiel auf die toString-Methode zugreifen, nicht aber auf die neu erstellte Testseigenschaft?

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:

Code kopieren Der Code lautet wie folgt:

//Prinzip der Ausführung von a.toString():

1. Wrapper = neue Zahl(a);
2. wrapper.toString(); // "10"
3. Wrapper löschen;

Wenn die Methode [[Put]] als Nächstes neue Attribute erstellt, erfolgt dies ebenfalls über das gepackte Objekt:
Code kopieren Der Code lautet wie folgt:

//Das Prinzip der Ausführung von a.test = 100:

1. Wrapper = neue Zahl(a);
2. wrapper.test = 100;
3. Wrapper löschen;

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:

Code kopieren Der Code lautet wie folgt:

//Prinzip der Ausführung eines.tests:

1. Wrapper = neue Zahl(a);
2. wrapper.test; // undefiniert

Diese Methode erklärt, wie der Originalwert gelesen wird. Wenn ein Originalwert häufig für den Zugriff auf Attribute verwendet wird, wird er aus Gründen der Zeiteffizienz direkt durch ein Objekt ersetzt, wenn nicht häufig darauf zugegriffen wird Für Berechnungszwecke kann dieses Formular beibehalten werden.

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:

Code kopieren Der Code lautet wie folgt:

alarm(1..toString()); // "1"

Wir wissen bereits, wie die Methode [[Get]] und die Eigenschaftszugriffsfunktionen funktionieren. Mal sehen, was passiert:

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

geerbt

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:

Code kopieren Der Code lautet wie folgt:

1.toString(); // Syntaxfehler!

(1).toString(); // OK

1..toString(); // OK

1['toString'](); // OK

Prototypenkette

Lassen Sie uns zeigen, wie Sie eine Prototypenkette für ein benutzerdefiniertes Objekt erstellen. Es ist ganz einfach:

Code kopieren Der Code lautet wie folgt:

Funktion A() {
alarm('A.[[Anruf]] aktiviert');
this.x = 10;
}
A.prototype.y = 20;

var a = new A();
Alert([a.x, a.y]); // 10 (selbst), 20 (geerbt)

Funktion B() {}

// Die neueste Methode der Prototypkette besteht darin, den Prototyp des Objekts auf ein anderes neues Objekt zu setzen
B.prototype = new A();

// Repariere die Konstruktor-Eigenschaft des Prototyps, sonst wäre es A
B.prototype.constructor = B;

var b = new B();
Alert([b.x, b.y]); // 10, 20, 2 werden vererbt

// [[Get]] b.x:
// b.x (nein) -->
// b.[[Prototyp]].x (ja) - 10

// [[Get]] b.y
// b.y (nein) -->
// b.[[Prototyp]].y (nein) -->
// b.[[Prototyp]].[[Prototyp]].y (ja) - 20

// wobei b.[[Prototyp]] === B.prototyp,
// und b.[[Prototype]].[[Prototype]] === A.prototype

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.

Code kopieren Der Code lautet wie folgt:

Funktion A(param) {
if (!param) {
throw 'Param erforderlich';
}
this.param = param;
}
A.prototype.x = 10;

var a = neues A(20);
Alert([a.x, a.param]); // 10, 20

Funktion B() {}
B.prototype = new A(); // Fehler

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.

Code kopieren Der Code lautet wie folgt:

Funktion A() {
alarm('A.[[Anruf]] aktiviert');
this.x = 10;
}
A.prototype.y = 20;

var a = new A();
alarm([a.x, a.y]); // 10 (selbst), 20 (integriert)

Funktion B() {
// Oder verwenden Sie A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}

// Vererbung: Prototypen über leere Zwischenkonstruktoren miteinander verbinden
var F = function () {};
F.prot
Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn