Warum wirft typeof nur manchmal ReferenceError?

8

In Chrome und Firefox

%Vor%

prüft auf 'undefined' .

Aber

%Vor%

gibt einen Fehler aus:

%Vor%

Das zerstört die Vorstellungen, die ich von der Vertretbarkeit von Ausdrücken habe! Bis jetzt kannte ich keine Bedingungen, für die foo und (function() { return foo; })() nicht gleich sind.

Ist das Standardverhalten? Wenn ja, wäre es hilfreich, den relevanten Teil des ECMAScript-Standards zu zitieren.

BEARBEITEN:

Ein anderes Beispiel:

%Vor%

Ich hätte erwartet, dass (foo) und (foo + 0) einen Fehler auslösen.

Aber der erste hat keinen Fehler; der zweite tut.

    
Paul Draper 10.06.2014, 20:58
quelle

5 Antworten

12

Grundsätzlich überprüft der Operator typeof , ob eine Variable¹ nicht aufgelöst werden kann und gibt "undefined" zurück. Das heißt, typeof gibt einen definierten Wert für nicht deklarierte Variablen¹ zurück, bevor der Algorithmus GetValue erreicht wird, der auf nicht deklarierte Variablen¹ verweist .

Zitieren ECMAScript 5.1 § 11.4.3 Der Typ des Operators (Hervorhebung hinzugefügt):

  

11.4.3 Der Typ des Operators

     

Die Produktion UnaryExpression : typeof UnaryExpression ist   wie folgt bewertet:

     
  1. Sei val das Ergebnis der Evaluierung von UnaryExpression .
  2.   
  3. Wenn Typ ( val ) Referenz , dann

         

    2.1. Wenn IsUnresolvableReference ( val ) true ist, geben Sie "undefined" .      

    2.2 Es sei val GetValue ( val ).

  4.   
  5. Gibt eine Zeichenfolge zurück, die durch Typ ( val ) gemäß Tabelle 20 .

  6.   

Auf der anderen Seite die return-Anweisung - wie die meisten Operatoren und Anweisungen, die den Wert aus dem Bezeichner lesen ( s) - ruft immer GetValue auf, was nicht lösbare Kennungen (nicht deklarierte Variablen) auslöst. Zitieren ECMAScript 5.1 § 8.7.1 GetValue (V) (Hervorhebung hinzugefügt):

  

8.7.1 GetValue (V)

     
  1. Wenn Typ (V) keine Referenz ist, geben Sie V zurück.
  2.   
  3. Basis ist das Ergebnis des Aufrufs von GetBase (V).
  4.   
  5. Wenn IsUnresolvableReference (V), werfen Sie eine ReferenceError Ausnahme aus.
  6.   

Analysieren Sie jetzt den Code:

%Vor%

Dieser Code wird ein Funktionsobjekt instanziieren, ausführen und nur dann wird typeof mit dem Rückgabewert der Funktion arbeiten (Funktionsaufruf dauert Priorität über den Operator typeof .

Daher wird der Code ausgegeben, während die return -Anweisung der IIFE ausgewertet wird, bevor die Operation typeof ausgewertet werden kann.

Ein ähnliches, aber einfacheres Beispiel:

%Vor%

Die Addition wird vor typeof ausgewertet. Dies führt zu einem Fehler, wenn der Zusatzoperator GetValue auf foo aufruft, bevor typeof ins Spiel kommt .

Jetzt:

%Vor%

Es wird kein Fehler ausgegeben, da der Gruppierungsoperator (Klammern) nichts per se "evaluiert" erzwingt nur Vorrang. Genauer gesagt ruft der Gruppierungsoperator nicht GetValue auf. Im obigen Beispiel wird eine (nicht auflösbare) Referenz zurückgegeben.

Die kommentierte ES5.1-Spezifikation fügt sogar eine Anmerkung hinzu:

  

HINWEIS Dieser Algorithmus wendet GetValue nicht auf das Ergebnis der Auswertung von Expression an . Die Hauptmotivation dafür ist, dass Operatoren wie delete und typeof auf eingeklammerte Ausdrücke angewendet werden können.

NB Ich habe diese Antwort geschrieben mit dem Schwerpunkt, eine einfache und verständliche Erklärung zu geben, den Fachjargon auf ein Minimum zu beschränken, aber immer noch ausreichend klar zu sein und die geforderten ECMAScript-Standardreferenzen bereitzustellen hoffe, eine hilfreiche Ressource für Entwickler zu sein, die Schwierigkeiten haben, den typeof -Operator zu verstehen.

¹ Der Begriff "Variable" wird verwendet, um das Verständnis zu erleichtern. Ein korrekterer Begriff wäre identifier , der nicht nur in eine Lexikalische Umgebung eingeführt werden kann durch Variablendeklarationen, aber auch Funktionsdeklarationen, Formalparameter, Aufruf einer Funktion ( arguments ), with / catch Blöcke, Zuordnung einer Eigenschaft zum globalen Objekt, let und const Anweisungen (ES6), und möglicherweise ein paar andere Möglichkeiten.

    
Fabrício Matté 10.06.2014, 21:17
quelle
8
  

Ist das Standardverhalten?

Ja. typeof gibt keinen Fehler aus, weil nur ein Wert zurückgegeben wird angegeben . Wie jedoch andere Antworten gesagt haben, schlägt der Code bei der Auswertung des Operanden fehl.

  

Wenn ja, wäre es hilfreich, den relevanten Teil des ECMAScript-Standards zu zitieren.

Bei der Auswertung des Funktionsausdrucks wird der Versuch, den Wert von foo aufzulösen (so dass er zurückgegeben werden kann), das interne GetValue Methode mit Argument foo . Da foo jedoch nicht deklariert oder anderweitig erstellt wurde, wird ein Referenzfehler ausgelöst.

Bearbeiten

Im Falle von:

%Vor%

"(" und ")" sind Punctuators B. eine (möglicherweise leere) Parameterliste angeben, wenn eine Funktion wie foo(a, b) aufgerufen wird, oder ein Ausdruck, der ausgewertet werden soll, z if (x < 0) und so weiter.

Im Fall von typeof (foo) bezeichnen sie einfach die Bewertung von foo , bevor Sie den Operator typeof anwenden. So wird foo , das ein gültiger Bezeichner ist, an typeof übergeben, wobei der obige Link, der versucht, es aufzulösen, nicht feststellen kann, ob es eine nicht löschbare Referenz und gibt die Zeichenfolge "undefined" zurück.

Im Falle von:

%Vor%

Die Klammern bewirken, dass der Ausdruck foo + 0 zuerst ausgewertet wird. Wenn der Wert von foo abgerufen wird, wird ein Referenzfehler ausgelöst, sodass typeof nicht funktioniert. Beachten Sie, dass ohne die Klammern:

%Vor%

aufgrund der Vorrangstellung des Operators: typeof foo gibt die Zeichenfolge "undefined" zurück, so dass + zu Additionsoperator weil eines der Argumente ein String ist, führt es eine Verkettung durch (die String-Version von addition, nicht die mathematische Version), also wird 0 in den String% konvertiert co_de% und verkettet zu "0" , reselling in der Zeichenfolge "undefined" .

Zu jeder Zeit, zu der die Auswertung eines Ausdrucks mit einer nicht auflösbaren Referenz versucht wird (z. B. eine nicht deklarierte oder initialisierte Variable), wird ein Referenzfehler ausgelöst, z. B.

%Vor%

löst auch einen Referenzfehler aus, um herauszufinden, was an typeof übergeben werden soll, muss der Ausdruck ausgewertet werden. Um den Operator "undefined0" anzuwenden, muss der Wert von foo erhalten werden, und bei dem Versuch wird ein Referenzfehler ausgelöst.

    
RobG 10.06.2014 21:12
quelle
2

Der Fehler "ReferenceError: foo ist nicht definiert" wird nicht von typeof ausgelöst, sondern von der Funktion selbst ausgelöst. Wenn du benutzt hättest:

%Vor%

es hätte "number" wie erwartet zurückgegeben, aber in diesem Beispiel erreicht JavaScript nicht einmal den Punkt, an dem typeof auf irgendwas ausgeführt wird. Sie erhalten den gleichen Fehler wie bei der Ausführung:

%Vor%     
Ssawa 10.06.2014 21:05
quelle
2

Wenn ich die Spezifikation durchschaue, denke ich, dass alles auf hinausläuft, wenn der betreffende Operator versucht, GetValue() für seinen Operanden auszuführen.

typeof versucht zuerst den Type seines Operanden zu ermitteln. Wenn dieser Typ ein Reference ist und IsUnresolvableReference() , dann wird ein Fehler ausgegeben und undefined zurückgegeben. Im Wesentlichen wird der Operand nicht vollständig ausgewertet; Wenn dies der Fall wäre, würde alles, was undefined war, eine Exception auslösen, stattdessen wird es kurzgeschlossen und gibt eine nette, nützliche Zeichenfolge zurück.

In den Beispielen werden die selbstausführenden Funktionen und der Aufruf des Additionsoperators GetValue ohne zuerst auf IsUnresolvableReference() like typeof überprüft: Sie rufen GetValue auf und werfen eine Ausnahme , wenn die Referenz nicht aufgelöst ist ( foo ist in unserem Fall undefined ) . (Ich denke! Das ist meine beste Schätzung beim Lesen der Spezifikation.)

    
ajm 10.06.2014 21:29
quelle
1

Dies ist Standardverhalten. Der Operator typeof nimmt fast eine Referenz der nächsten Variablen, die Sie übergeben.

Versuchen wir also typeof foo .

Der Javascript-Interpreter betrachtet typeof und findet den Typ von foo .

Jetzt versuchen wir typeof (function() { return foo })()

Der Javascript-Interpreter betrachtet typeof. Da der Ausdruck danach keine Variable ist, wertet er den Ausdruck aus. (function() { return foo })() gibt ReferenceError aus, weil foo nicht definiert ist. Wenn es möglich wäre, die Referenz einer Variablen, d. H. Etwas wie (function() { return *foo })() , zu übergeben, würde dies nicht passieren.

Hinweis: Demnach könnte man denken, dass typeof (foo) einen Fehler werfen würde, da (foo) keine Variable ist und ausgewertet werden muss, aber das ist falsch; typeof (foo) gibt auch "undefined" zurück, wenn foo nicht definiert ist.

Im Wesentlichen wertet der Interpreter die nächste Variable, aber nicht den Ausdruck, in einem "sicheren" Kontext aus, so dass typeof keinen Fehler auslöst.

Es ist ein bisschen verwirrend.

    
soktinpk 10.06.2014 21:05
quelle