Warum ist 0 [0] syntaktisch gültig?

119

Warum ist diese Zeile in Javascript gültig?

var a = 0[0];

Danach aist undefined.

Michael M.
quelle
4
als true[0]oder ""[0]
Hacketo
24
@CodeAngry Um fair zu sein, JavaScript wurde in HTML geboren, und es ist HTML, das die ganze Sache "Wirf alles, was du willst, auf mich und ich werde versuchen, einen Sinn daraus zu machen" begann.
Niet the Dark Absol
8
@NiettheDarkAbsol fair zu sein sind Sie einfach falsch , wie die Syntax macht Sinn machen (noch nicht so viel). Hiermit wird nur die Eigenschaft "0"eines new Number(0)Objekts abgerufen.
Meandre
Es ist eine falsche Annahme, dass a immer undefiniert sein wird. Es ist durchaus möglich, 0[0]einen Wert zurückzugeben
Rune FS
@meandre 0["toString"]Das ist großartig, danke, dass du darauf hingewiesen hast .
Jonathan

Antworten:

169

Wenn Sie dies tun 0[0], verwandelt der JS-Interpreter den ersten 0in ein NumberObjekt und versucht dann, auf die [0]Eigenschaft dieses Objekts zuzugreifen undefined.

Es liegt kein Syntaxfehler vor, da die Eigenschaftszugriffssyntax 0[0]in diesem Zusammenhang von der Sprachgrammatik zugelassen wird. Diese Struktur (unter Verwendung von Begriffen in der Javascript-Grammatik) ist NumericLiteral[NumericLiteral].

Der relevante Teil der Sprachgrammatik aus Abschnitt A.3 der ES5 ECMAScript-Spezifikation ist folgender:

Literal ::
    NullLiteral
    BooleanLiteral
    NumericLiteral
    StringLiteral
    RegularExpressionLiteral

PrimaryExpression :
    this
    Identifier
    Literal
    ArrayLiteral
    ObjectLiteral
    ( Expression )

MemberExpression :
    PrimaryExpression
    FunctionExpression
    MemberExpression [ Expression ]
    MemberExpression . IdentifierName
    new MemberExpression Arguments    

So kann man dem Grammatiker durch diesen Fortschritt folgen:

MemberExpression [ Expression ]
PrimaryExpression [ Expression ]
Literal [ Expression ]
NumericLiteral [ Expression ]

Und ähnlich Expressionkann es auch sein, wenn NumericLiteralwir der Grammatik folgen, sehen wir, dass dies erlaubt ist:

NumericLiteral [ NumericLiteral ]

0[0]Dies bedeutet, dass dies ein zulässiger Teil der Grammatik und somit kein SyntaxError ist.


Zur Laufzeit dürfen Sie dann eine Eigenschaft lesen, die nicht vorhanden ist (sie wird nur als gelesen undefined), solange die Quelle, aus der Sie lesen, entweder ein Objekt ist oder eine implizite Konvertierung in ein Objekt aufweist. Und ein numerisches Literal hat tatsächlich eine implizite Konvertierung in ein Objekt (ein Zahlenobjekt).

Dies ist eine der oft unbekannten Funktionen von Javascript. Die Typen Number, Booleanund Stringin Javascript sind in der Regel intern als Primitive (nicht ausgewachsene Objekte) gespeichert. Hierbei handelt es sich um eine kompakte, unveränderliche Speicherdarstellung (wahrscheinlich auf diese Weise aus Gründen der Implementierungseffizienz). Javascript möchte jedoch, dass Sie diese Grundelemente wie Objekte mit Eigenschaften und Methoden behandeln können. Wenn Sie also versuchen, auf eine Eigenschaft oder Methode zuzugreifen, die vom Grundelement nicht direkt unterstützt wird, zwingt Javascript das Grundelement vorübergehend in einen geeigneten Objekttyp, wobei der Wert auf den Wert des Grundelements festgelegt wird.

Wenn Sie eine objektähnliche Syntax für ein Grundelement wie verwenden 0[0], erkennt der Interpreter dies als Eigenschaftszugriff für ein Grundelement. Die Antwort darauf besteht darin, das erste 0numerische Grundelement zu einem vollständigen NumberObjekt zu zwingen, auf das es dann auf die [0]Eigenschaft zugreifen kann . In diesem speziellen Fall ist die [0]Eigenschaft eines Number-Objekts undefinedder Grund, weshalb Sie diesen Wert erhalten 0[0].

Hier ist ein Artikel über die automatische Konvertierung eines Grundelements in ein Objekt zum Zwecke des Umgangs mit Eigenschaften:

Das geheime Leben von Javascript-Primitiven


Hier sind die relevanten Teile der ECMAScript 5.1-Spezifikation:

9.10 CheckObjectCoercible

Löst TypeError aus, wenn value undefinedoder ist null, andernfalls wird zurückgegeben true.

Geben Sie hier die Bildbeschreibung ein

11.2.1 Property Accessors

  1. BaseReference sei das Ergebnis der Auswertung von MemberExpression.
  2. Sei baseValue GetValue (baseReference).
  3. Es sei propertyNameReference das Ergebnis der Auswertung von Expression.
  4. Es sei propertyNameValue GetValue (propertyNameReference).
  5. Rufen Sie CheckObjectCoercible (baseValue) auf.
  6. Es sei propertyNameString ToString (propertyNameValue).
  7. Wenn die syntaktische Produktion, die ausgewertet wird, im Code für den strengen Modus enthalten ist, sei strict wahr, andernfalls sei strict falsch.
  8. Gibt einen Wert vom Typ Referenz zurück, dessen Basiswert baseValue ist und dessen referenzierter Name propertyNameString ist und dessen strikter Modus-Flag streng ist.

Ein operativer Teil für diese Frage ist Schritt 5 oben.

8.7.1 GetValue (V)

Hier wird beschrieben, wie beim Aufrufen des Werts als Eigenschaftsreferenz ToObject(base)die Objektversion eines beliebigen Grundelements abgerufen wird .

9.9 ToObject

Diese beschreibt , wie Boolean, Numberund StringPrimitiven sind an eine Objektform mit der umgewandelt [[PrimitiveValue]] internem Eigenschaftssatz entsprechend.


Als interessanter Test, wenn der Code so war:

var x = null;
var a = x[0];

Es wäre immer noch keine Syntax zur Analysezeit werfen , da dies technisch rechtliche Syntax ist, aber es wäre eine Typeerror zur Laufzeit werfen , wenn Sie den Code ausführen , weil , wenn die oben Eigenschaftenaccessoren Logik auf den Wert angewandt wird x, wird es nennen CheckObjectCoercible(x)oder rufen Sie ToObject(x)die wird sowohl eine Typeerror aus , wenn xist nulloder undefined.

jfriend00
quelle
0[1,2]ist auch gültig, was bedeutet das? (Ich aktualisiere die Frage)
Michael M.
Und es wird kein Syntaxfehler ausgelöst, da der Zugriff auf Eigenschaften für alles, was nicht nulloder undefinedvöllig in Ordnung ist, auch wenn diese Eigenschaften nicht vorhanden sind.
user4642212
6
@ Michael keine Notwendigkeit zu aktualisieren. Das ist ein Komma-Operator, also ist es nur0[2]
Amit Joki
1
Komma-Operator: wertet sowohl 1 als auch 2 in aus 1,2, gibt aber 2 zurück.
user4642212
2
Das ist eine großartige Antwort auf die Nuance einer Frage.
tbh__
20

Wie die meisten Programmiersprachen verwendet JS eine Grammatik, um Ihren Code zu analysieren und in eine ausführbare Form zu konvertieren. Wenn die Grammatik keine Regel enthält, die auf einen bestimmten Codeabschnitt angewendet werden kann, wird ein SyntaxError ausgelöst. Andernfalls wird der Code als gültig angesehen, unabhängig davon, ob er sinnvoll ist oder nicht.

Die relevanten Teile der JS-Grammatik sind

Literal :: 
   NumericLiteral
   ...

PrimaryExpression :
   Literal
   ...

MemberExpression :
   PrimaryExpression
   MemberExpression [ Expression ]
   ...

Da 0[0]es diesen Regeln entspricht, wird es als gültiger Ausdruck betrachtet. Ob es richtig ist (z. B. zur Laufzeit keinen Fehler auslöst), ist eine andere Geschichte, aber ja, das ist es. So bewertet JS Ausdrücke wie someLiteral[someExpression]:

  1. bewerten someExpression(was beliebig komplex sein kann)
  2. Konvertieren Sie das Literal in einen entsprechenden Objekttyp (numerische Literale => Number, Strings => Stringusw.)
  3. Rufen Sie die get propertyOperation für Ergebnis (2) mit dem Eigenschaftsnamen Ergebnis (1) auf.
  4. Ergebnis verwerfen (2)

So 0[0]wird interpretiert als

index = 0
temp = Number(0)
result = getproperty(temp, index) // it's undefined, but JS doesn't care
delete temp
return result

Hier ist ein Beispiel für einen gültigen , aber falschen Ausdruck:

null[0]

Es wird gut analysiert, aber zur Laufzeit schlägt der Interpreter in Schritt 2 fehl (da er nullnicht in ein Objekt konvertiert werden kann) und gibt einen Laufzeitfehler aus.

georg
quelle
1
Es steckt mehr dahinter. var x = null; var a = x[0];generiert keinen Syntaxfehler, löst aber zur Laufzeit einen TypeError aus.
jfriend00
@ jfriend00: Darum ging es bei der Frage nicht, aber hinzugefügt.
Georg
Das Ergebnis muss nicht undefiniert sein. Es ist möglich, 0[0]einen Wert anstelle von undefiniert zurückzugeben
Rune FS
9

Es gibt Situationen, in denen Sie eine Zahl in Javascript gültig abonnieren können:

-> 0['toString']
function toString() { [native code] }

Obwohl nicht sofort ersichtlich ist, warum Sie dies tun möchten, entspricht das Abonnieren in Javascript der Verwendung der gepunkteten Notation (obwohl die Punktnotation Sie auf die Verwendung von Bezeichnern als Schlüssel beschränkt).

Duncan
quelle
@AmitJoki Dies ist dasselbe wie (0).toString(ohne die Funktion aufzurufen). Es ist eine Eigenschaft vom Typ Nummer.
user4642212
@AmitJoki, weil es die Frage beantwortet, warum diese Zeile gültig ist.
Duncan
@ Duncan, aber dies ist mehr von "Was ist Klammer Notation" und ich nehme an, OP weiß es. Die Tatsache , dass es als Number - Objekt interpretiert hat und dann seine 0ist Eigenschaft zugegriffen , und da es nicht existiert, undefinedist mehr richtig als in jfriend00 erläutert.
Amit Joki
@AmitJoki Es ist eine falsche Annahme, 0[0]die undefiniert zurückgibt . Es ist wahrscheinlich, dass es wird, aber es muss nicht der Fall sein
Rune FS
9

Ich möchte nur darauf hinweisen, dass diese gültige Syntax in keiner Weise nur für Javascript gilt. Die meisten Sprachen haben einen Laufzeitfehler oder einen Typfehler, aber das ist nicht dasselbe wie ein Syntaxfehler. Javascript gibt in vielen Situationen, in denen eine andere Sprache möglicherweise eine Ausnahme auslöst, undefiniert zurück, auch wenn ein Objekt abonniert wird, das keine Eigenschaft mit dem angegebenen Namen hat.

Die Syntax kennt den Typ eines Ausdrucks nicht (selbst einen einfachen Ausdruck wie ein numerisches Literal) und ermöglicht es Ihnen, einen beliebigen Operator auf einen beliebigen Ausdruck anzuwenden. Zum Beispiel der Versuch, einen Index in Javascript zu erstellen undefinedoder zu nullverursachen TypeError. Es ist kein Syntaxfehler - wenn dieser niemals ausgeführt wird (auf der falschen Seite einer if-Anweisung), verursacht er keine Probleme, während ein Syntaxfehler per Definition immer zur Kompilierungszeit abgefangen wird (eval, Function usw.) , alle zählen als kompiliert).

Random832
quelle
8

Weil es eine gültige Syntax ist und sogar gültiger Code interpretiert werden muss. Sie können versuchen, auf jede Eigenschaft eines Objekts zuzugreifen (und in diesem Fall wird 0 in ein Number-Objekt umgewandelt), und es gibt Ihnen den Wert, falls vorhanden, andernfalls undefiniert. Der Versuch, auf eine Eigenschaft von undefined zuzugreifen, funktioniert jedoch nicht, sodass 0 [0] [0] zu einem Laufzeitfehler führen würde. Dies würde jedoch weiterhin als gültige Syntax klassifiziert. Es gibt einen Unterschied zwischen der gültigen Syntax und dem, was keine Laufzeit- / Kompilierungsfehler verursacht.

Clox
quelle
3

Die Syntax ist nicht nur gültig, das Ergebnis muss auch nicht undefinedin den meisten, wenn nicht allen vernünftigen Fällen sein. JS ist eine der reinsten objektorientierten Sprachen. Die meisten sogenannten OO-Sprachen sind klassenorientiert, in dem Sinne, dass Sie die Form (die an die Klasse gebunden ist) des einmal erstellten Objekts nicht ändern können, sondern nur den Status des Objekts. In JS können Sie sowohl den Status als auch die Form des Objekts ändern, und dies tun Sie häufiger als Sie denken. Diese Fähigkeit führt zu einem ziemlich undurchsichtigen Code, wenn Sie ihn missbrauchen. Ziffern sind unveränderlich, sodass Sie das Objekt selbst nicht ändern können, weder den Status noch die Form, damit Sie dies tun können

0[0] = 1;

Dies ist ein gültiger Zuweisungsausdruck, der 1 zurückgibt, aber eigentlich nichts 0zuweist. Die Ziffer ist unveränderlich. Was an sich etwas seltsam ist. Sie können einen gültigen und korrekten (ausführbaren) Assingment-Ausdruck haben, der nichts (*) zuweist. Der Typ der Ziffer ist jedoch ein veränderbares Objekt, sodass Sie den Typ mutieren können, und die Änderungen werden die Prototypkette entlang kaskadieren.

Number[0] = 1;
//print 1 to the console
console.log(0[0]);
//will also print 1 to the console because all integers have the same type
console.log(1[0]); 

Natürlich ist es weit entfernt von der Kategorie der vernünftigen Verwendung, aber die Sprache ist angegeben, um dies zu ermöglichen, da in anderen Szenarien die Erweiterung der Objektfunktionen tatsächlich sehr sinnvoll ist. Auf diese Weise werden jQuery-Plugins in das jQuery-Objekt eingebunden, um ein Beispiel zu geben.

(*) Es weist tatsächlich den Wert 1 der Eigenschaft eines Objekts zu, es gibt jedoch keine Möglichkeit, auf dieses (vorübergehende) Objekt zu verweisen, und es wird daher beim nächsten GC-Durchlauf gesammelt

Rune FS
quelle
3

In JavaScript ist alles ein Objekt. Wenn der Interpreter es analysiert, behandelt er 0 als Objekt und versucht, 0 als Eigenschaft zurückzugeben. Das gleiche passiert, wenn Sie versuchen, auf das 0. Element von true oder "" (leere Zeichenfolge) zuzugreifen.

Selbst wenn Sie 0 [0] = 1 setzen, werden die Eigenschaft und ihr Wert im Speicher festgelegt, aber während Sie auf 0 zugreifen, wird sie als Zahl behandelt (Verwechseln Sie hier nicht die Behandlung als Objekt und Zahl.)

Laxmikant Dange
quelle