Das Geheimnis der Funktionen in Javascript kann nicht gelöst werden

16

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 .

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. 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. Es fing an, wie ein Dilemma von Henne und Huhn zu klingen.

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.prototypeeine Funktion eigentlich ein Objekt?

Außerdem ist laut Mozilla-Dokumentation jedes Javascript functioneine Erweiterung des FunctionObjekts, aber wenn Sie console.log(Function.prototype.constructor)es erneut ausführen, handelt es sich um eine Funktion. Wie kannst du nun etwas benutzen, um es selbst zu erschaffen (Mind = Blown)?

Das Letzte, was Function.prototypeist eine Funktion, aber ich kann auf die constructorFunktion zugreifen , indem ich Function.prototype.constructortue, das heißt, es Function.prototypeist eine Funktion, die das prototypeObjekt zurückgibt

Umair Abid
quelle
Da eine Funktion ein Objekt ist, bedeutet dies, dass Function.prototypesie 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 wie Function.prototypeund Object.prototypemöglicherweise nur auf besondere Weise von der Engine interpretiert werden.
Walfrat
1
Wenn Sie keinen standardkonformen JavaScript-Compiler implementieren möchten, müssen Sie sich wirklich keine Sorgen machen. Wenn Sie etwas Nützliches erledigen möchten, sind Sie vom Kurs abgekommen.
Jared Smith
5
Zu Ihrer Information: Die übliche englische Formulierung für "das Dilemma von Henne und Huhn" ist "das Henne-Ei-Problem", nämlich "Was war zuerst da, das Huhn oder das Ei?". (Natürlich ist die Antwort das Ei. Ovipare Tiere gab es schon seit Millionen von Jahren vor Hühnern.)
Eric Lippert

Antworten:

32

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 Feiner 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 prototypeMitglied eines Objekts ist normalerweise nicht der Prototyp des Objekts.
  • Vielmehr ist das prototypeElement 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 ObjectistFunction.prototype
  • Object.prototype ist das Objektprototypobjekt.
  • Der Prototyp von Object.prototypeistnull
  • Der Prototyp von Functionist Function.prototype- dies ist eine der seltenen Situationen, in denen Function.prototypetatsächlich der Prototyp von ist Function!
  • Function.prototype ist das Funktionsprototypobjekt.
  • Der Prototyp von Function.prototypeistObject.prototype

Nehmen wir an, wir machen eine Funktion Foo.

  • Der Prototyp von Fooist Function.prototype.
  • Foo.prototype ist das Foo-Prototypobjekt.
  • Der Prototyp von Foo.prototypeist 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 prototypeEigenschaft von".

Bildbeschreibung hier eingeben

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 instanceofdas? honda instanceof Carbedeutet "ist Car.prototypegleich einem Objekt in hondader Prototypenkette?"

Ja ist es. hondaDer Prototyp ist Car.prototype, also sind wir fertig. Dies druckt wahr.

Was ist mit dem zweiten?

honda.constructorexistiert nicht, so konsultieren wir den Prototyp, der ist Car.prototype. Beim Car.prototypeErstellen des Objekts wurde ihm automatisch die Eigenschaft " constructorgleich" Carzugewiesen. 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 Reptilebedeutet "ist Reptile.prototypegleich einem Objekt in lizardder Prototypenkette?"

Ja ist es. lizardDer 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 lizardmit konstruiert wurde, new Reptileaber Sie würden sich irren. Grund es aus.

  • Hat lizardeine constructorImmobilie? Nein, deshalb schauen wir uns den Prototyp an.
  • Der Prototyp von lizardist Reptile.prototype, was ist Animal.
  • Hat Animaleine constructorImmobilie? Also schauen wir uns den Prototyp an.
  • Der Prototyp von Animalist Object.prototype, und Object.prototype.constructorwird 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.prototypeeine 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.prototypeder FunctionPrototyp ist. Also Function.prototype()ist das legal und wenn du es tust, kommst du undefinedzurück. Es ist also eine Funktion.

Der ObjectPrototyp 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.constructorist einfach Functionoffensichtlich. Und Functionist 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 constructorKanten 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" myprototypezu 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 .mycallablejedem Objekt eine Eigenschaft hinzu, die angibt, ob es aufrufbar ist oder nicht. So einfach ist das.

Eric Lippert
quelle
9
Zum Schluss noch eine kurze, prägnante und leicht verständliche Erklärung von JavaScript! Ausgezeichnet! Wie könnte einer von uns möglicherweise verwirrt sein? :) Obwohl im Ernst, ist das letzte bisschen über Objekte, die Nachschlagetabellen sind, wirklich der Schlüssel. Es gibt eine Methode für den Wahnsinn --- aber es ist immer noch Wahnsinn ...
Greg Burghardt
4
@ GregBurghardt: Ich stimme zu, dass es auf den ersten Blick komplex aussieht, aber die Komplexität ist die Folge einfacher Regeln. Jedes Objekt hat eine __proto__. Der __proto__des Objektprototyps ist null. Das __proto__von new X()ist X.prototype. Alle Funktionsobjekte haben den Funktionsprototyp mit __proto__Ausnahme des Funktionsprototyps. Objectund Functionund der Funktionsprototyp sind Funktionen. Diese Regeln sind alle unkompliziert und bestimmen die Topologie des Diagramms der ursprünglichen Objekte.
Eric Lippert
6

Sie haben bereits viele ausgezeichnete Antworten, aber ich möchte nur eine kurze und klare Antwort auf Ihre Antwort geben, wie all dies funktioniert, und diese Antwort lautet:

MAGIE!!!

Wirklich, das war's.

Die Leute , die Ausführung Motoren ECMAScript implementieren müssen implementieren ECMAScript-Regeln, aber nicht zu halten , indem sie in deren Umsetzung.

Die ECMAScript-Spezifikation besagt, dass A von B erbt, B jedoch eine Instanz von A? Kein Problem! Erstellen Sie zuerst A mit einem Prototypzeiger von NULL, erstellen Sie B als Instanz von A, und richten Sie den Prototypzeiger von A so ein, dass er anschließend auf B zeigt. Kinderleicht.

Sie sagen, aber warten Sie, es gibt keine Möglichkeit, den Prototypzeiger in ECMAScript zu ändern! Aber hier ist die Sache: Dieser Code läuft nicht auf der ECMAScript-Engine, dieser Code ist die ECMAScript-Engine. Es hat Zugriff auf Interna der Objekte, über die ECMAScript-Code, der auf der Engine ausgeführt wird, nicht verfügt. Kurzum: Er kann machen, was er will.

Übrigens, wenn Sie es wirklich wollen, müssen Sie dies nur einmal tun: Danach können Sie beispielsweise Ihren internen Speicher sichern und diesen bei jedem Start Ihrer ECMAScript-Engine laden.

Dies gilt auch dann, wenn die ECMAScript-Engine selbst in ECMAScript geschrieben wurde (wie dies beispielsweise bei Mozilla Narcissus der Fall ist). Selbst dann hat der ECMAScript-Code, der die Engine implementiert , noch uneingeschränkten Zugriff auf die von ihr implementierte Engine , obwohl er natürlich keinen Zugriff auf die Engine hat , auf der sie ausgeführt wird .

Jörg W. Mittag
quelle
3

Aus ECMA-Spezifikation 1

ECMAScript enthält keine geeigneten Klassen wie die in C ++, Smalltalk oder Java, sondern unterstützt Konstruktoren, die Objekte durch Ausführen von Code erstellen, der Speicher für die Objekte zuweist, und initialisiert sie ganz oder teilweise, indem ihren Eigenschaften Anfangswerte zugewiesen werden. Alle Funktionen einschließlich Konstruktoren sind Objekte, aber nicht alle Objekte sind Konstruktoren.

Ich sehe nicht ein, wie es klarer sein könnte !!! </sarcasm>

Weiter unten sehen wir:

Prototyp Ein Prototyp ist ein Objekt, mit dem die Struktur-, Status- und Verhaltensvererbung in ECMAScript implementiert wird. Wenn ein Konstruktor ein Objekt erstellt, verweist dieses Objekt implizit auf den zugehörigen Prototyp des Konstruktors, um Eigenschaftsreferenzen aufzulösen. Der dem Konstruktor zugeordnete Prototyp kann durch den Programmausdruck constructor.prototype referenziert werden, und Eigenschaften, die dem Prototyp eines Objekts hinzugefügt werden, werden durch Vererbung von allen Objekten gemeinsam genutzt, die den Prototyp gemeinsam nutzen.

So können wir sehen, dass ein Prototyp ein Objekt ist, aber nicht unbedingt ein Funktionsobjekt.

Wir haben auch diesen interessanten Leckerbissen

http://www.ecma-international.org/ecma-262/8.0/index.html#sec-object-objects

Der Object-Konstruktor ist das% Object% -eigene Objekt und der Anfangswert der Object-Eigenschaft des globalen Objekts.

und

Der Function-Konstruktor ist das% Function% -Intrinsic-Objekt und der Anfangswert der Function-Eigenschaft des globalen Objekts.

Ewan
quelle
Jetzt schon. Mit ECMA6 können Sie Klassen erstellen und daraus Objekte instanziieren.
Ncmathsadist
2
@ncmathsadist ES6-Klassen sind nur ein syntaktischer Zucker, die Semantik ist dieselbe.
Hamza Fatmi
1
Ihr sarcasmSpitzname ansonsten ist dieser Text für einen Anfänger wirklich ziemlich undurchsichtig.
Robert Harvey
Richtig, ich werde später mehr hinzufügen, ich muss ein bisschen graben
Ewan
1
ähm? um darauf hinzuweisen, dass es aus dem Dokument nicht klar ist
Ewan
1

Die folgenden Typen umfassen jeden Wert in JavaScript:

  • boolean
  • number
  • undefined(die den einzelnen Wert enthält undefined)
  • string
  • symbol (abstrakte eindeutige "Dinge", die durch Referenz verglichen werden)
  • object

Jedes Objekt (dh alles) in JavaScript hat einen Prototyp, der eine Art Objekt ist.

Der Prototyp enthält Funktionen, die auch eine Art Objekt 1 sind .

Objekte haben auch einen Konstruktor, der eine Funktion und damit eine Art Objekt ist.

verschachtelt

Es ist alles rekursiv, aber die Implementierung kann dies automatisch tun, da sie im Gegensatz zu JavaScript-Code Objekte erstellen kann, ohne JavaScript-Funktionen aufrufen zu müssen (da Objekte nur Speicher sind, den die Implementierung steuert).

Die meisten Objektsysteme in vielen dynamisch typisierten Sprachen sind wie folgt zirkulär 2 . Zum Beispiel in Python, sind Klassen - Objekte und die Klasse der Klassen ist type, so typeist deshalb eine Instanz von sich selbst.

Die beste Idee ist, nur die Tools zu verwenden, die die Sprache bereitstellt, und nicht zu viel darüber nachzudenken, wie sie dorthin gelangt sind.

1 Funktionen sind etwas Besonderes, weil sie aufrufbar sind und die einzigen Werte sind, die undurchsichtige Daten enthalten können (ihren Körper und möglicherweise einen Abschluss).

2 Eigentlich handelt es sich eher um ein gequältes, nach hinten gebogenes, verzweigtes Band, aber "rund" ist nah genug.

Challenger5
quelle