Ich versuche, hinter den Kulissen von Javascript zu verstehen und die Entstehung eingebauter Objekte, insbesondere Objekt und Funktion, und die Beziehung zwischen ihnen zu verstehen.
Es ist kompliziert, leicht zu missverstehen und viele Anfänger-Javascript-Bücher verstehen es falsch. Vertrauen Sie also nicht allem, was Sie lesen.
Ich war einer der Implementierer der JS-Engine von Microsoft in den neunziger Jahren und Mitglied des Standardisierungsausschusses und habe eine Reihe von Fehlern begangen, als ich diese Antwort zusammengestellt habe. (Obwohl ich seit über 15 Jahren nicht mehr daran gearbeitet habe, kann mir vielleicht vergeben werden.) Es ist kniffliges Zeug. Aber sobald Sie die Vererbung von Prototypen verstanden haben, ist alles sinnvoll.
Als ich las, dass alle eingebauten Objekte wie Array, String usw. Erweiterungen (geerbt) von Object sind, ging ich davon aus, dass Object das erste eingebaute Objekt ist, das erstellt wird, und der Rest der Objekte von ihm erbt.
Werfen Sie zunächst alles weg, was Sie über klassenbasierte Vererbung wissen. JS verwendet prototypbasierte Vererbung.
Stellen Sie als nächstes sicher, dass Sie in Ihrem Kopf eine sehr klare Definition dessen haben, was "Vererbung" bedeutet. Leute, die an OO-Sprachen wie C # oder Java oder C ++ gewöhnt sind, denken, dass Vererbung Subtypisierung bedeutet, aber Vererbung bedeutet nicht Subtypisierung. Vererbung bedeutet, dass die Mitglieder einer Sache auch Mitglieder einer anderen Sache sind . Dies bedeutet nicht unbedingt , dass zwischen diesen Dingen eine Untertypisierungsbeziehung besteht! So viele Missverständnisse in der Typentheorie sind das Ergebnis von Menschen, die nicht erkennen, dass es einen Unterschied gibt.
Es macht jedoch keinen Sinn, wenn Sie wissen, dass Objekte nur durch Funktionen erstellt werden können, aber dann sind Funktionen auch nichts anderes als Objekte von Function.
Das ist einfach falsch. Einige Objekte werden nicht durch Aufrufen new F
einer Funktion erstellt F
. Einige Objekte werden von der JS-Laufzeit aus dem Nichts erstellt. Es gibt Eier, die von keinem Huhn gelegt wurden . Sie wurden gerade von der Laufzeit erstellt, als sie gestartet wurde.
Sagen wir, was die Regeln sind und vielleicht wird das helfen.
- Jede Objektinstanz hat ein Prototypobjekt.
- In einigen Fällen kann dieser Prototyp sein
null
.
- Wenn Sie auf ein Element in einer Objektinstanz zugreifen und das Objekt nicht über dieses Element verfügt, wird das Objekt auf den Prototyp verschoben oder angehalten, wenn der Prototyp null ist.
- Das
prototype
Mitglied eines Objekts ist normalerweise nicht der Prototyp des Objekts.
- Vielmehr ist das
prototype
Element eines Funktionsobjekts F das Objekt, das zum Prototyp des von erstellten Objekts wird new F()
.
- In einigen Implementierungen erhalten Instanzen ein
__proto__
Mitglied, das wirklich ihren Prototyp angibt. (Dies ist jetzt veraltet. Verlassen Sie sich nicht darauf.)
- Funktionsobjekten wird beim Erstellen ein brandneues Standardobjekt zugewiesen
prototype
.
- Der Prototyp eines Funktionsobjekts ist natürlich
Function.prototype
.
Fassen wir zusammen.
- Der Prototyp von
Object
istFunction.prototype
Object.prototype
ist das Objektprototypobjekt.
- Der Prototyp von
Object.prototype
istnull
- Der Prototyp von
Function
ist Function.prototype
- dies ist eine der seltenen Situationen, in denen Function.prototype
tatsächlich der Prototyp von ist Function
!
Function.prototype
ist das Funktionsprototypobjekt.
- Der Prototyp von
Function.prototype
istObject.prototype
Nehmen wir an, wir machen eine Funktion Foo.
- Der Prototyp von
Foo
ist Function.prototype
.
Foo.prototype
ist das Foo-Prototypobjekt.
- Der Prototyp von
Foo.prototype
ist Object.prototype
.
Nehmen wir an, wir sagen new Foo()
- Der Prototyp des neuen Objekts ist
Foo.prototype
Stellen Sie sicher, dass dies sinnvoll ist. Lass es uns zeichnen. Ovale sind Objektinstanzen. Kanten __proto__
bedeuten entweder "der Prototyp von" oder prototype
"die prototype
Eigenschaft von".
Alles, was Sie zur Laufzeit tun müssen, ist, alle diese Objekte zu erstellen und ihre verschiedenen Eigenschaften entsprechend zuzuweisen. Ich bin sicher, Sie können sehen, wie das gemacht werden würde.
Schauen wir uns nun ein Beispiel an, das Ihr Wissen testet.
function Car(){ }
var honda = new Car();
print(honda instanceof Car);
print(honda.constructor == Car);
Was druckt das?
Nun, was heißt instanceof
das? honda instanceof Car
bedeutet "ist Car.prototype
gleich einem Objekt in honda
der Prototypenkette?"
Ja ist es. honda
Der Prototyp ist Car.prototype
, also sind wir fertig. Dies druckt wahr.
Was ist mit dem zweiten?
honda.constructor
existiert nicht, so konsultieren wir den Prototyp, der ist Car.prototype
. Beim Car.prototype
Erstellen des Objekts wurde ihm automatisch die Eigenschaft " constructor
gleich" Car
zugewiesen. Dies ist also der Fall.
Was ist nun damit?
var Animal = new Object();
function Reptile(){ }
Reptile.prototype = Animal;
var lizard = new Reptile();
print(lizard instanceof Reptile);
print(lizard.constructor == Reptile);
Was druckt dieses Programm?
Wieder lizard instanceof Reptile
bedeutet "ist Reptile.prototype
gleich einem Objekt in lizard
der Prototypenkette?"
Ja ist es. lizard
Der Prototyp ist Reptile.prototype
, also sind wir fertig. Dies druckt wahr.
Und jetzt?
print(lizard.constructor == Reptile);
Sie könnten denken, dass dies auch wahr ist, da lizard
mit konstruiert wurde, new Reptile
aber Sie würden sich irren. Grund es aus.
- Hat
lizard
eine constructor
Immobilie? Nein, deshalb schauen wir uns den Prototyp an.
- Der Prototyp von
lizard
ist Reptile.prototype
, was ist Animal
.
- Hat
Animal
eine constructor
Immobilie? Also schauen wir uns den Prototyp an.
- Der Prototyp von
Animal
ist Object.prototype
, und Object.prototype.constructor
wird zur Laufzeit erstellt und ist gleich Object
.
- Das gibt also falsch aus.
Wir hätten es Reptile.prototype.constructor = Reptile;
irgendwann sagen sollen , aber daran haben wir uns nicht erinnert!
Stellen Sie sicher, dass alles für Sie sinnvoll ist. Zeichne ein paar Kästchen und Pfeile, wenn es immer noch verwirrend ist.
Die andere äußerst verwirrende Sache ist, wenn ich console.log(Function.prototype)
eine Funktion drucke, aber wenn ich sie drucke console.log(Object.prototype)
, druckt sie ein Objekt. Warum ist Function.prototype
eine Funktion eigentlich ein Objekt?
Der Funktionsprototyp ist als eine Funktion definiert, die beim Aufruf zurückgegeben wird undefined
. Wir wissen bereits, dass dies seltsamerweise Function.prototype
der Function
Prototyp ist. Also Function.prototype()
ist das legal und wenn du es tust, kommst du undefined
zurück. Es ist also eine Funktion.
Der Object
Prototyp besitzt diese Eigenschaft nicht. es ist nicht aufrufbar. Es ist nur ein Objekt.
bei dir ist console.log(Function.prototype.constructor)
es wieder eine funktion.
Function.prototype.constructor
ist einfach Function
offensichtlich. Und Function
ist eine Funktion.
Wie kannst du nun etwas benutzen, um es selbst zu erschaffen (Mind = Blown)?
Sie denken darüber nach . Es ist lediglich erforderlich, dass die Laufzeit beim Start eine Reihe von Objekten erstellt. Objekte sind lediglich Nachschlagetabellen, die Zeichenfolgen mit Objekten verknüpfen. Wenn die Laufzeit startet, alles hat, es zu tun ist , ein paar Dutzend leere Objekte erstellen und dann die Start zuweisen prototype
, __proto__
, constructor
, und so weiter Eigenschaften jedes Objekts , bis sie das Diagramm zu machen , dass sie machen müssen.
Es ist hilfreich, wenn Sie das Diagramm, das ich Ihnen oben gegeben habe, nehmen und ihm constructor
Kanten hinzufügen . Sie werden schnell feststellen, dass dies ein sehr einfaches Objektdiagramm ist und dass die Laufzeitumgebung keine Probleme damit hat, es zu erstellen.
Eine gute Übung wäre es, es selbst zu tun. Hier, ich werde dich anfangen. Wir werden verwenden my__proto__
, um "das Prototypobjekt von" und "die Prototypeneigenschaft von" myprototype
zu bedeuten.
var myobjectprototype = new Object();
var myfunctionprototype = new Object();
myfunctionprototype.my__proto__ = myobjectprototype;
var myobject = new Object();
myobject.myprototype = myobjectprototype;
Und so weiter. Können Sie den Rest des Programms ausfüllen, um eine Gruppe von Objekten zu erstellen, die dieselbe Topologie wie die "echten" in Javascript eingebauten Objekte haben? Wenn Sie dies tun, werden Sie feststellen, dass es extrem einfach ist.
Objekte in JavaScript sind nur Nachschlagetabellen, die Zeichenfolgen mit anderen Objekten verknüpfen . Das ist es! Hier gibt es keine Magie. Sie werden in Knoten gefesselt, weil Sie sich Einschränkungen vorstellen, die eigentlich nicht existieren, so dass jedes Objekt von einem Konstruktor erstellt werden musste.
Funktionen sind nur Objekte, die eine zusätzliche Fähigkeit haben: aufgerufen zu werden. Gehen Sie also Ihr kleines Simulationsprogramm durch und fügen Sie .mycallable
jedem Objekt eine Eigenschaft hinzu, die angibt, ob es aufrufbar ist oder nicht. So einfach ist das.
Function.prototype
sie eine Funktion sein kann und innere Felder hat. Nein, Sie führen die Prototypfunktion nicht aus, wenn Sie die Struktur durchgehen. Denken Sie schließlich daran, dass es eine Engine gibt, die Javascript interpretiert, sodass Objekt und Funktion wahrscheinlich innerhalb der Engine erstellt werden und nicht aus Javascript und speziellen Referenzen wieFunction.prototype
undObject.prototype
möglicherweise nur auf besondere Weise von der Engine interpretiert werden.